Skip to main content

blitz_dom/node/
node.rs

1use crate::Document;
2use crate::layout::damage::HoistedPaintChildren;
3use bitflags::bitflags;
4use blitz_traits::events::{
5    BlitzPointerEvent, BlitzPointerId, DomEventData, HitResult, PointerCoords,
6};
7use blitz_traits::node_id::NodeId;
8use blitz_traits::shell::ShellProvider;
9use euclid::{Point2D, Rect, Size2D};
10use html_escape::encode_quoted_attribute_to_string;
11use keyboard_types::Modifiers;
12use kurbo::{Affine, Rect as KurboRect};
13use markup5ever::{LocalName, local_name};
14use parley::{BreakReason, Cluster, ClusterSide, Selection};
15use selectors::matching::ElementSelectorFlags;
16use std::cell::{Cell, RefCell};
17use std::fmt::Write;
18use std::ops::{Deref, Range};
19use std::sync::Arc;
20use std::sync::atomic::{AtomicBool, Ordering};
21use style::Atom;
22use style::computed_values::isolation::T as Isolation;
23use style::invalidation::element::restyle_hints::RestyleHint;
24use style::properties::ComputedValues;
25use style::properties::generated::longhands::position::computed_value::T as Position;
26use style::selector_parser::{PseudoElement, RestyleDamage};
27use style::servo_arc::Arc as ServoArc;
28use style::shared_lock::SharedRwLock;
29use style::stylesheets::UrlExtraData;
30use style::values::computed::CSSPixelLength;
31use style::values::computed::Display as StyloDisplay;
32use style::values::computed::Rotate;
33use style::values::generics::transform::{Scale, Translate};
34use style::values::specified::box_::{DisplayInside, DisplayOutside};
35use style_dom::ElementState;
36use style_traits::values::ToCss;
37use taffy::{
38    Cache,
39    prelude::{Layout, Style},
40};
41use thin_vec::ThinVec;
42
43use super::stylo_data::StyloData;
44use super::{Attribute, DocumentData, ElementData};
45
46#[derive(Clone, Copy)]
47enum OutputStyle {
48    Normal,
49    Pretty,
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum DisplayOuter {
54    Block,
55    Inline,
56    None,
57}
58
59bitflags! {
60    #[derive(Clone, Copy, PartialEq)]
61    pub struct NodeFlags: u32 {
62        /// Whether the node is the root node of an Inline Formatting Context
63        const IS_INLINE_ROOT = 0b00000001;
64        /// Whether the node is the root node of an Table formatting context
65        const IS_TABLE_ROOT = 0b00000010;
66        /// Whether the node is "in the document" (~= has a parent and isn't a template node)
67        const IS_IN_DOCUMENT = 0b00000100;
68    }
69}
70
71impl NodeFlags {
72    #[inline(always)]
73    pub fn is_inline_root(&self) -> bool {
74        self.contains(Self::IS_INLINE_ROOT)
75    }
76
77    #[inline(always)]
78    pub fn is_table_root(&self) -> bool {
79        self.contains(Self::IS_TABLE_ROOT)
80    }
81
82    #[inline(always)]
83    pub fn is_in_document(&self) -> bool {
84        self.contains(Self::IS_IN_DOCUMENT)
85    }
86
87    #[inline(always)]
88    pub fn reset_construction_flags(&mut self) {
89        self.remove(Self::IS_INLINE_ROOT);
90        self.remove(Self::IS_TABLE_ROOT);
91    }
92}
93
94pub struct Node {
95    // The actual tree we belong to. This is unsafe!!
96    tree: *mut crate::NodeTree,
97
98    /// Our Id
99    pub id: NodeId,
100    /// Our parent's ID
101    pub parent: Option<NodeId>,
102    // What are our children?
103    pub children: ThinVec<NodeId>,
104    /// Our parent in the layout hierachy: a separate list that includes anonymous collections of inline elements
105    pub layout_parent: Cell<Option<NodeId>>,
106    /// A separate child list that includes anonymous collections of inline elements
107    pub layout_children: RefCell<Option<ThinVec<NodeId>>>,
108    /// Anonymous block boxes created for this node during layout construction.
109    ///
110    /// Anonymous blocks live only in the slab (they are not part of the DOM
111    /// `children` list), so we track the ones we own here to be able to
112    /// deallocate them when this node is reconstructed.
113    pub anonymous_blocks: ThinVec<NodeId>,
114    /// The same as layout_children, but sorted by z-index
115    pub paint_children: RefCell<Option<ThinVec<NodeId>>>,
116    pub stacking_context: Option<Box<HoistedPaintChildren>>,
117
118    /// The "flattened tree" children of this node used for layout and painting,
119    /// if it differs from [`children`](Self::children). This is set for shadow
120    /// hosts (where it holds the shadow root's children) and `<slot>` elements
121    /// (where it holds the light-DOM nodes assigned to the slot). When `None`,
122    /// [`children`](Self::children) is used directly.
123    #[cfg(feature = "shadow-dom")]
124    pub flattened_children: Option<Vec<NodeId>>,
125
126    // Flags
127    pub flags: NodeFlags,
128
129    /// Node type (Element, TextNode, etc) specific data.
130    ///
131    /// For element nodes this holds the [`ElementData`], which stores most of
132    /// the per-node style/layout state. For the document node it holds the
133    /// [`DocumentData`]. Access the moved fields through the forwarding methods
134    /// on [`Node`] (e.g. [`Node::style`], [`Node::final_layout`]).
135    pub data: NodeData,
136}
137
138unsafe impl Send for Node {}
139unsafe impl Sync for Node {}
140
141/// Generates forwarding accessors for fields that live on both [`ElementData`]
142/// (element / anonymous block nodes) and [`DocumentData`] (the document node).
143macro_rules! universal_accessors {
144    ($($(#[$meta:meta])* $field:ident / $field_mut:ident : $ty:ty),* $(,)?) => {
145        impl Node {
146            $(
147                $(#[$meta])*
148                #[inline]
149                pub fn $field(&self) -> &$ty {
150                    match &self.data {
151                        NodeData::Element(data) | NodeData::AnonymousBlock(data) => &data.$field,
152                        NodeData::Document(data) => &data.$field,
153                        _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
154                    }
155                }
156
157                $(#[$meta])*
158                #[inline]
159                pub fn $field_mut(&mut self) -> &mut $ty {
160                    match &mut self.data {
161                        NodeData::Element(data) | NodeData::AnonymousBlock(data) => &mut data.$field,
162                        NodeData::Document(data) => &mut data.$field,
163                        _ => panic!(concat!("`", stringify!($field), "` is not available on this node kind")),
164                    }
165                }
166            )*
167        }
168    };
169}
170
171universal_accessors! {
172    stylo_element_data / stylo_element_data_mut: StyloData,
173    style / style_mut: Style<Atom>,
174    style_source / style_source_mut: Option<ServoArc<ComputedValues>>,
175    subtree_hoists / subtree_hoists_mut: bool,
176    // `cache` is not here: it is stored as `Option<Box<Cache>>` and reached
177    // through hand-written accessors below, because the read side has to be
178    // able to answer without allocating. See `ElementData::cache`.
179    unrounded_layout / unrounded_layout_mut: Layout,
180    final_layout / final_layout_mut: Layout,
181    scroll_offset / scroll_offset_mut: crate::Point<f64>,
182    scrollable_overflow / scrollable_overflow_mut: KurboRect,
183    transform / transform_mut: Option<Affine>,
184    display_constructed_as / display_constructed_as_mut: StyloDisplay,
185    // The document node is styled/snapshotted like an element, so it also
186    // carries these:
187    element_state / element_state_mut: ElementState,
188    snapshot_handled / snapshot_handled_mut: AtomicBool,
189    // `apply_selector_flags` deposits `for_parent()` flags on the parent node,
190    // and the parent of the root <html> element is the document -- so the
191    // document has to be able to hold selector flags too.
192    selector_flags / selector_flags_mut: Cell<ElementSelectorFlags>,
193}
194
195impl Node {
196    /// This node's taffy layout cache.
197    ///
198    /// Hand-written rather than generated by `universal_accessors!` because
199    /// the cache is `Option<Box<Cache>>`: a node that has never been laid out
200    /// borrows a shared empty one instead of owning 1616 bytes. See the
201    /// [`cache`](super::ElementData::cache) field for the measurement behind
202    /// that. The signature is unchanged, so callers cannot tell.
203    #[inline]
204    pub fn cache(&self) -> &Cache {
205        match &self.data {
206            NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache(),
207            NodeData::Document(data) => data.cache(),
208            _ => panic!("`cache` is not available on this node kind"),
209        }
210    }
211
212    /// This node's taffy layout cache, allocating on first use.
213    #[inline]
214    pub fn cache_mut(&mut self) -> &mut Cache {
215        match &mut self.data {
216            NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache_mut(),
217            NodeData::Document(data) => data.cache_mut(),
218            _ => panic!("`cache_mut` is not available on this node kind"),
219        }
220    }
221
222    /// Release this node's layout cache, returning its memory.
223    #[inline]
224    pub fn cache_release(&mut self) {
225        match &mut self.data {
226            NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.cache_release(),
227            NodeData::Document(data) => data.cache_release(),
228            _ => {}
229        }
230    }
231
232    /// Style data from stylo, if this node kind carries it (element or document
233    /// nodes). Returns `None` for text/comment nodes.
234    /// The computed values the cached taffy style was built from, for node
235    /// kinds that carry one. `None` for text and comment nodes, which are never
236    /// styled, so a caller can ask without knowing the kind.
237    #[inline]
238    pub fn style_source_opt(&self) -> Option<&ServoArc<ComputedValues>> {
239        match &self.data {
240            NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.style_source.as_ref(),
241            NodeData::Document(data) => data.style_source.as_ref(),
242            _ => None,
243        }
244    }
245
246    #[inline]
247    pub fn stylo_element_data_opt(&self) -> Option<&StyloData> {
248        match &self.data {
249            NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
250                Some(&data.stylo_element_data)
251            }
252            NodeData::Document(data) => Some(&data.stylo_element_data),
253            _ => None,
254        }
255    }
256
257    #[inline]
258    pub fn stylo_element_data_opt_mut(&mut self) -> Option<&mut StyloData> {
259        match &mut self.data {
260            NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
261                Some(&mut data.stylo_element_data)
262            }
263            NodeData::Document(data) => Some(&mut data.stylo_element_data),
264            _ => None,
265        }
266    }
267
268    /// The `dirty_descendants` flag, if this node kind carries it (element or
269    /// document nodes). Returns `None` for text/comment nodes.
270    #[inline]
271    fn dirty_descendants_flag(&self) -> Option<&AtomicBool> {
272        match &self.data {
273            NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
274                Some(&data.dirty_descendants)
275            }
276            NodeData::Document(data) => Some(&data.dirty_descendants),
277            _ => None,
278        }
279    }
280
281    /// The document's shared style lock. Only available on element and
282    /// document nodes.
283    #[inline]
284    pub fn guard(&self) -> &SharedRwLock {
285        let guard = match &self.data {
286            NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.guard.as_ref(),
287            NodeData::Document(data) => data.guard.as_ref(),
288            _ => None,
289        };
290        guard.expect("`guard` is not available on this node kind")
291    }
292
293    #[inline]
294    pub fn has_snapshot(&self) -> bool {
295        match &self.data {
296            NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot,
297            NodeData::Document(data) => data.has_snapshot,
298            _ => false,
299        }
300    }
301
302    #[inline]
303    pub fn set_has_snapshot(&mut self, value: bool) {
304        match &mut self.data {
305            NodeData::Element(data) | NodeData::AnonymousBlock(data) => data.has_snapshot = value,
306            NodeData::Document(data) => data.has_snapshot = value,
307            _ => {}
308        }
309    }
310
311    #[inline]
312    pub fn before(&self) -> Option<NodeId> {
313        self.element_data().and_then(|data| data.before)
314    }
315
316    #[inline]
317    pub fn after(&self) -> Option<NodeId> {
318        self.element_data().and_then(|data| data.after)
319    }
320}
321
322impl Node {
323    pub(crate) fn new(
324        tree: *mut crate::NodeTree,
325        id: NodeId,
326        guard: SharedRwLock,
327        mut data: NodeData,
328    ) -> Self {
329        // Store a handle to the document's shared style lock on the node data.
330        // Both element and document nodes are styled by stylo and so need it.
331        match &mut data {
332            NodeData::Element(data) | NodeData::AnonymousBlock(data) => {
333                data.guard = Some(guard);
334            }
335            NodeData::Document(data) => data.guard = Some(guard),
336            _ => {}
337        }
338
339        Self {
340            tree,
341
342            id,
343            parent: None,
344            children: ThinVec::new(),
345            layout_parent: Cell::new(None),
346            layout_children: RefCell::new(None),
347            anonymous_blocks: ThinVec::new(),
348            paint_children: RefCell::new(None),
349            stacking_context: None,
350            #[cfg(feature = "shadow-dom")]
351            flattened_children: None,
352
353            flags: NodeFlags::empty(),
354            data,
355        }
356    }
357
358    pub fn set_transform(&mut self, scale: f32) -> Option<Affine> {
359        let transform = self.primary_styles().and_then(|s| {
360            let size = self.final_layout().size;
361            let reference_box = Rect::new(
362                Point2D::new(CSSPixelLength::new(0.0), CSSPixelLength::new(0.0)),
363                Size2D::new(
364                    CSSPixelLength::new(size.width),
365                    CSSPixelLength::new(size.height),
366                ),
367            );
368            // Resolve the transform in CSS pixels, then convert it to device-pixel space
369            // (S * T * S^-1): translation components are scaled, linear components are not.
370            crate::resolve_2d_transform(s.get_box(), reference_box).map(|t| {
371                let scale = scale as f64;
372                let [m11, m12, m21, m22, m41, m42] = t.as_coeffs();
373                Affine::new([m11, m12, m21, m22, m41 * scale, m42 * scale])
374            })
375        });
376
377        *self.transform_mut() = transform;
378        transform
379    }
380
381    pub fn pe_by_index(&self, index: usize) -> Option<NodeId> {
382        match index {
383            0 => self.after(),
384            1 => self.before(),
385            _ => panic!("Invalid pseudo element index"),
386        }
387    }
388
389    pub fn set_pe_by_index(&mut self, index: usize, value: Option<NodeId>) {
390        let Some(data) = self.element_data_mut() else {
391            return;
392        };
393        match index {
394            0 => data.after = value,
395            1 => data.before = value,
396            _ => panic!("Invalid pseudo element index"),
397        }
398    }
399
400    pub(crate) fn display_style(&self) -> Option<StyloDisplay> {
401        Some(self.primary_styles().as_ref()?.clone_display())
402    }
403
404    /// A compact computed-style view for renderer diagnostics.
405    /// Computed values for the properties a page is most likely to read.
406    ///
407    /// Deliberately a fixed set rather than every property stylo computes.
408    /// `getComputedStyle` in a real browser answers for all of them, and this
409    /// does not; a caller asking for something outside this list gets an empty
410    /// string, which is what an unsupported property returns anyway.
411    ///
412    /// The alternative was to keep throwing `getComputedStyle is not defined`,
413    /// which stops the whole script. Measured over a hundred-site corpus, three
414    /// sites died on exactly that. A page that reads `display` and gets the
415    /// right answer is strictly better off, and one that reads something absent
416    /// is no worse off than before.
417    ///
418    /// Grow the list when a page is found needing more. It is cheap: every
419    /// entry is one `clone_*` call stylo already provides.
420    pub fn computed_style_properties(&self) -> Option<Vec<(&'static str, String)>> {
421        let style = self.primary_styles()?;
422        let layout = *self.final_layout();
423        let (used_width, used_height) = if matches!(
424            style.clone_box_sizing(),
425            style::computed_values::box_sizing::T::BorderBox
426        ) {
427            (layout.size.width, layout.size.height)
428        } else {
429            (
430                (layout.size.width
431                    - layout.padding.left
432                    - layout.padding.right
433                    - layout.border.left
434                    - layout.border.right)
435                    .max(0.0),
436                (layout.size.height
437                    - layout.padding.top
438                    - layout.padding.bottom
439                    - layout.border.top
440                    - layout.border.bottom)
441                    .max(0.0),
442            )
443        };
444        Some(vec![
445            ("display", style.clone_display().to_css_string()),
446            ("position", style.clone_position().to_css_string()),
447            ("visibility", style.clone_visibility().to_css_string()),
448            ("opacity", style.clone_opacity().to_css_string()),
449            ("color", style.clone_color().to_css_string()),
450            (
451                "background-color",
452                style.clone_background_color().to_css_string(),
453            ),
454            (
455                "font-size",
456                format!("{}px", style.clone_font_size().computed_size().px()),
457            ),
458            ("font-weight", style.clone_font_weight().to_css_string()),
459            ("font-style", style.clone_font_style().to_css_string()),
460            ("font-family", style.clone_font_family().to_css_string()),
461            ("text-align", style.clone_text_align().to_css_string()),
462            ("overflow-x", style.clone_overflow_x().to_css_string()),
463            ("overflow-y", style.clone_overflow_y().to_css_string()),
464            ("z-index", style.clone_z_index().to_css_string()),
465            ("box-sizing", style.clone_box_sizing().to_css_string()),
466            (
467                "flex-direction",
468                style.clone_flex_direction().to_css_string(),
469            ),
470            (
471                "justify-content",
472                style.clone_justify_content().to_css_string(),
473            ),
474            ("align-items", style.clone_align_items().to_css_string()),
475            // The used values, not the specified ones. A page reading `width`
476            // off `getComputedStyle` is nearly always asking how wide the box
477            // ended up, and `auto` is not an answer it can use.
478            //
479            // Which box follows `box-sizing`, as in a browser: the content box
480            // unless the element is `border-box`. Reporting the border box for
481            // every element made `width - paddingLeft - paddingRight` (the
482            // usual content-width arithmetic) subtract the padding twice.
483            ("width", format!("{}px", used_width)),
484            ("height", format!("{}px", used_height)),
485            // The box model's edges, used values in px. Absent, a read of
486            // `paddingLeft` came back `undefined`, and `parseFloat(undefined)`
487            // is `NaN`, which then travelled silently into whatever position or
488            // size the page was computing: measuring code that subtracts
489            // padding or margins from a box placed things at NaN.
490            ("padding-top", format!("{}px", layout.padding.top)),
491            ("padding-right", format!("{}px", layout.padding.right)),
492            ("padding-bottom", format!("{}px", layout.padding.bottom)),
493            ("padding-left", format!("{}px", layout.padding.left)),
494            ("border-top-width", format!("{}px", layout.border.top)),
495            ("border-right-width", format!("{}px", layout.border.right)),
496            ("border-bottom-width", format!("{}px", layout.border.bottom)),
497            ("border-left-width", format!("{}px", layout.border.left)),
498            ("margin-top", format!("{}px", layout.margin.top)),
499            ("margin-right", format!("{}px", layout.margin.right)),
500            ("margin-bottom", format!("{}px", layout.margin.bottom)),
501            ("margin-left", format!("{}px", layout.margin.left)),
502        ])
503    }
504
505    pub fn diagnostic_computed_style(&self) -> Option<Vec<(&'static str, String)>> {
506        let style = self.primary_styles()?;
507        Some(vec![
508            ("display", style.clone_display().to_css_string()),
509            ("color", style.clone_color().to_css_string()),
510            (
511                "background-color",
512                style.clone_background_color().to_css_string(),
513            ),
514            (
515                "font-size",
516                format!("{}px", style.clone_font_size().computed_size().px()),
517            ),
518            ("width", style.clone_width().to_css_string()),
519        ])
520    }
521
522    /// Whether computed style removes this node from layout.
523    pub fn is_display_none(&self) -> bool {
524        self.display_style()
525            .is_some_and(|display| display.is_none())
526    }
527
528    pub fn is_or_contains_block(&self) -> bool {
529        let style = self.primary_styles();
530        let style = style.as_ref();
531
532        // Ignore out-of-flow items
533        let position = style
534            .map(|s| s.clone_position())
535            .unwrap_or(Position::Relative);
536        let is_in_flow = matches!(
537            position,
538            Position::Static | Position::Relative | Position::Sticky
539        );
540        if !is_in_flow {
541            return false;
542        }
543        // Floated boxes do not break up the inline flow: they participate in the
544        // inline formatting context as out-of-flow inline boxes
545        let is_floating = style
546            .map(|s| s.clone_float().is_floating())
547            .unwrap_or(false);
548        if is_floating {
549            return false;
550        }
551        let display = style
552            .map(|s| s.clone_display())
553            .unwrap_or(StyloDisplay::inline());
554        match display.outside() {
555            DisplayOutside::None => false,
556            DisplayOutside::Block => true,
557            _ => {
558                if display.inside() == DisplayInside::Flow {
559                    self.children
560                        .iter()
561                        .copied()
562                        .any(|child_id| self.tree()[child_id].is_or_contains_block())
563                } else {
564                    false
565                }
566            }
567        }
568    }
569
570    pub fn is_whitespace_node(&self) -> bool {
571        match &self.data {
572            NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
573            _ => false,
574        }
575    }
576
577    pub fn is_focussable(&self) -> bool {
578        self.data
579            .downcast_element()
580            .map(|el| el.is_focussable)
581            .unwrap_or(false)
582    }
583
584    pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
585        if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
586            if let Some(mut element_data) = stylo_element_data.get_mut() {
587                element_data.hint.insert(hint);
588            }
589        }
590        // Mark all ancestors as having dirty descendants so the style traversal
591        // will visit this node's subtree
592        self.mark_ancestors_dirty();
593    }
594
595    /// Returns whether this node has any descendants that need restyling.
596    pub fn has_dirty_descendants(&self) -> bool {
597        self.dirty_descendants_flag()
598            .is_some_and(|flag| flag.load(Ordering::Relaxed))
599    }
600
601    /// Sets the dirty_descendants flag on this node.
602    pub fn set_dirty_descendants(&self) {
603        if let Some(flag) = self.dirty_descendants_flag() {
604            flag.store(true, Ordering::Relaxed);
605        }
606    }
607
608    /// Clears the dirty_descendants flag on this node.
609    pub fn unset_dirty_descendants(&self) {
610        if let Some(flag) = self.dirty_descendants_flag() {
611            flag.store(false, Ordering::Relaxed);
612        }
613    }
614
615    /// Set appropriate damage for Stylo when an element's style attribute is updated
616    pub(crate) fn mark_style_attr_updated(&mut self) {
617        if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
618            if let Some(mut data) = stylo_element_data.get_mut() {
619                data.hint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
620            }
621        }
622        self.set_dirty_descendants();
623        self.mark_ancestors_dirty();
624    }
625
626    /// Marks all ancestors of this node as having dirty descendants.
627    /// This propagates the dirty flag up the tree so that the style traversal
628    /// knows to visit the subtree containing this node.
629    pub fn mark_ancestors_dirty(&self) {
630        let mut current_id = self.parent;
631        while let Some(parent_id) = current_id {
632            let parent = &self.tree()[parent_id];
633            // If this ancestor already has dirty_descendants set, we can stop
634            // because all further ancestors must also have it set
635            if let Some(flag) = parent.dirty_descendants_flag() {
636                if flag.swap(true, Ordering::Relaxed) {
637                    break;
638                }
639            }
640            current_id = parent.parent;
641        }
642    }
643
644    // pub fn damage_mut(&mut self) -> Option<&mut RestyleDamage> {
645    //     self.stylo_element_data
646    //         .get_mut()
647    //         .map(|mut data: ElementDataMut<'a>| &'a mut data.damage)
648    // }
649
650    pub fn damage(&self) -> Option<RestyleDamage> {
651        self.stylo_element_data_opt()
652            .and_then(|stylo| stylo.get().map(|data| data.damage))
653    }
654
655    pub fn set_damage(&mut self, damage: RestyleDamage) {
656        if let Some(stylo) = self.stylo_element_data_opt_mut() {
657            if let Some(mut data) = stylo.get_mut() {
658                data.damage = damage;
659            }
660        }
661    }
662
663    pub fn insert_damage(&mut self, damage: RestyleDamage) {
664        if let Some(stylo) = self.stylo_element_data_opt_mut() {
665            if let Some(mut data) = stylo.get_mut() {
666                data.damage |= damage;
667            }
668        }
669    }
670
671    pub fn remove_damage(&mut self, damage: RestyleDamage) {
672        if let Some(stylo) = self.stylo_element_data_opt_mut() {
673            if let Some(mut data) = stylo.get_mut() {
674                data.damage.remove(damage);
675            }
676        }
677    }
678
679    pub fn clear_damage_mut(&mut self) {
680        if let Some(stylo) = self.stylo_element_data_opt_mut() {
681            if let Some(mut data) = stylo.get_mut() {
682                data.damage = RestyleDamage::empty();
683            }
684        }
685    }
686
687    pub fn hover(&mut self) {
688        if let Some(data) = self.element_data_mut() {
689            data.element_state.insert(ElementState::HOVER);
690        }
691        self.set_restyle_hint(RestyleHint::restyle_subtree());
692    }
693
694    pub fn unhover(&mut self) {
695        if let Some(data) = self.element_data_mut() {
696            data.element_state.remove(ElementState::HOVER);
697        }
698        self.set_restyle_hint(RestyleHint::restyle_subtree());
699    }
700
701    pub fn is_hovered(&self) -> bool {
702        self.element_data()
703            .is_some_and(|data| data.element_state.contains(ElementState::HOVER))
704    }
705
706    pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
707        if let Some(data) = self.element_data_mut() {
708            data.element_state
709                .insert(ElementState::FOCUS | ElementState::FOCUSRING);
710        }
711        self.set_restyle_hint(RestyleHint::restyle_subtree());
712
713        // If focussing a text input, enable IME and set IME area
714        if self
715            .element_data()
716            .and_then(|elem| elem.text_input_data())
717            .is_some()
718        {
719            shell_provider.set_ime_enabled(true);
720            let mut pos = self.absolute_position(0.0, 0.0);
721            pos.x += self.final_layout().content_box_x();
722            pos.y += self.final_layout().content_box_y();
723            let width = self.final_layout().content_box_width();
724            let height = self.final_layout().content_box_height();
725            shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
726        }
727    }
728
729    pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
730        if let Some(data) = self.element_data_mut() {
731            data.element_state
732                .remove(ElementState::FOCUS | ElementState::FOCUSRING);
733        }
734        self.set_restyle_hint(RestyleHint::restyle_subtree());
735
736        // If blurring a text input, disable IME
737        if self
738            .element_data()
739            .and_then(|elem| elem.text_input_data())
740            .is_some()
741        {
742            shell_provider.set_ime_enabled(false);
743        }
744    }
745
746    pub fn is_focussed(&self) -> bool {
747        self.element_data()
748            .is_some_and(|data| data.element_state.contains(ElementState::FOCUS))
749    }
750
751    pub fn active(&mut self) {
752        if let Some(data) = self.element_data_mut() {
753            data.element_state.insert(ElementState::ACTIVE);
754        }
755        self.set_restyle_hint(RestyleHint::restyle_subtree());
756    }
757
758    pub fn unactive(&mut self) {
759        if let Some(data) = self.element_data_mut() {
760            data.element_state.remove(ElementState::ACTIVE);
761        }
762        self.set_restyle_hint(RestyleHint::restyle_subtree());
763    }
764
765    pub fn is_active(&self) -> bool {
766        self.element_data()
767            .is_some_and(|data| data.element_state.contains(ElementState::ACTIVE))
768    }
769
770    // Marks the node as disabled if it can be.
771    // It does not disable any children which should be disabled as well (relevant for the `select` element).
772    pub fn disable(&mut self) {
773        if let Some(data) = self.element_data_mut() {
774            if data.can_be_disabled() {
775                data.element_state.insert(ElementState::DISABLED);
776                data.element_state.remove(ElementState::ENABLED);
777            }
778        }
779        self.set_restyle_hint(RestyleHint::restyle_subtree());
780    }
781
782    // Marks the node as enabled if it can be.
783    // It does not enable any children which should be enabled as well (relevant for the `select` element).
784    pub fn enable(&mut self) {
785        if let Some(data) = self.element_data_mut() {
786            if data.can_be_disabled() {
787                data.element_state.insert(ElementState::ENABLED);
788                data.element_state.remove(ElementState::DISABLED);
789            }
790        }
791        self.set_restyle_hint(RestyleHint::restyle_subtree());
792    }
793
794    pub fn subdoc(&self) -> Option<&dyn Document> {
795        self.element_data().and_then(|el| el.sub_doc_data())
796    }
797
798    pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
799        self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
800    }
801
802    pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
803        // For single-line inputs, add an offset to vertically center the text input layout
804        // within the content box of it's node.
805        if let Some(input_data) = self
806            .data
807            .downcast_element()
808            .and_then(|el| el.text_input_data())
809        {
810            if !input_data.is_multiline {
811                let content_box_height = self.final_layout().content_box_height();
812                let input_height = input_data.editor.try_layout().unwrap().height() / scale as f32;
813                let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
814
815                return y_offset as f64;
816            }
817        }
818
819        0.0
820    }
821}
822
823#[derive(Debug, Clone, Copy, PartialEq)]
824pub enum NodeKind {
825    Document,
826    Element,
827    AnonymousBlock,
828    Text,
829    Comment,
830    DocumentFragment,
831    ShadowRoot,
832}
833
834/// How much text one click selects.
835///
836/// Click count decides: a second click takes the word, a third takes the line,
837/// matching what a text input does and what every other platform does with the
838/// same gesture.
839#[derive(Debug, Clone, Copy, PartialEq, Eq)]
840pub enum TextGranularity {
841    /// The word under the pointer, by Unicode word segmentation.
842    Word,
843    /// The whole hard line, so a soft-wrapped paragraph selects entire.
844    Line,
845}
846
847impl TextGranularity {
848    /// The granularity a click of this count selects, or `None` for a first
849    /// click, which places a caret rather than selecting anything.
850    pub fn from_click_count(count: u16) -> Option<Self> {
851        match count {
852            0 | 1 => None,
853            2 => Some(Self::Word),
854            _ => Some(Self::Line),
855        }
856    }
857}
858
859/// The encapsulation mode of a shadow root.
860///
861/// Mirrors the `ShadowRootMode` enum from the DOM specification.
862#[derive(Debug, Clone, Copy, PartialEq, Eq)]
863pub enum ShadowRootMode {
864    /// Elements of the shadow root are accessible from JavaScript outside the
865    /// root (e.g. via `Element.shadowRoot`).
866    Open,
867    /// Elements of the shadow root are not accessible from JavaScript outside
868    /// the root.
869    Closed,
870}
871
872/// Data associated with a [`NodeData::ShadowRoot`] node.
873///
874/// A shadow root is a non-element, non-document node that acts as the root of a
875/// shadow tree. Its `children` (on the owning [`Node`]) are the top-level nodes
876/// of the shadow tree. The light-DOM children of the host element are
877/// distributed into any `<slot>` elements within this tree to form the
878/// "flattened tree" that is used for style resolution, layout and painting.
879#[derive(Debug, Clone)]
880pub struct ShadowRootData {
881    /// The node id of the host element that this shadow root is attached to.
882    pub host: NodeId,
883    /// The encapsulation mode of this shadow root.
884    pub mode: ShadowRootMode,
885    /// Node ids of `<style>` elements within this shadow root, in document
886    /// order. Used to build the scoped stylesheet set for this shadow tree.
887    pub stylesheet_nodes: Vec<NodeId>,
888}
889
890impl ShadowRootData {
891    pub fn new(host: NodeId, mode: ShadowRootMode) -> Self {
892        Self {
893            host,
894            mode,
895            stylesheet_nodes: Vec::new(),
896        }
897    }
898}
899
900/// The different kinds of nodes in the DOM.
901#[derive(Debug, Clone)]
902pub enum NodeData {
903    /// The `Document` itself - the root node of a HTML document.
904    Document(Box<DocumentData>),
905
906    /// An element with attributes.
907    Element(Box<ElementData>),
908
909    /// An anonymous block box
910    AnonymousBlock(Box<ElementData>),
911
912    /// A text node.
913    Text(TextNodeData),
914
915    /// A comment.
916    Comment {
917        /// The textual content of the comment
918        contents: String,
919    },
920
921    /// A `DocumentFragment`: a parentless container whose children move when
922    /// it is inserted, and which never appears in the box tree itself.
923    DocumentFragment,
924
925    /// The root of a shadow tree attached to a host element.
926    ShadowRoot(ShadowRootData),
927    // /// A `DOCTYPE` with name, public id, and system id. See
928    // /// [document type declaration on wikipedia][https://en.wikipedia.org/wiki/Document_type_declaration]
929    // Doctype { name: String, public_id: String, system_id: String },
930
931    // /// A Processing instruction.
932    // ProcessingInstruction { target: String, contents: String },
933}
934
935impl NodeData {
936    pub fn downcast_element(&self) -> Option<&ElementData> {
937        match self {
938            Self::Element(data) => Some(data),
939            Self::AnonymousBlock(data) => Some(data),
940            _ => None,
941        }
942    }
943
944    pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
945        match self {
946            Self::Element(data) => Some(data),
947            Self::AnonymousBlock(data) => Some(data),
948            _ => None,
949        }
950    }
951
952    pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
953        let Some(elem) = self.downcast_element() else {
954            return false;
955        };
956        *name == elem.name.local
957    }
958
959    pub fn attrs(&self) -> Option<&[Attribute]> {
960        Some(&self.downcast_element()?.attrs)
961    }
962
963    pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
964        self.downcast_element()?.attr(name)
965    }
966
967    pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
968        self.downcast_element()
969            .is_some_and(|elem| elem.has_attr(name))
970    }
971
972    pub fn kind(&self) -> NodeKind {
973        match self {
974            NodeData::Document(_) => NodeKind::Document,
975            NodeData::Element(_) => NodeKind::Element,
976            NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
977            NodeData::Text(_) => NodeKind::Text,
978            NodeData::Comment { .. } => NodeKind::Comment,
979            NodeData::DocumentFragment => NodeKind::DocumentFragment,
980            NodeData::ShadowRoot(_) => NodeKind::ShadowRoot,
981        }
982    }
983
984    pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
985        match self {
986            Self::ShadowRoot(data) => Some(data),
987            _ => None,
988        }
989    }
990
991    pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
992        match self {
993            Self::ShadowRoot(data) => Some(data),
994            _ => None,
995        }
996    }
997}
998
999#[derive(Debug, Clone)]
1000pub struct TextNodeData {
1001    /// The textual content of the text node
1002    pub content: String,
1003}
1004
1005impl TextNodeData {
1006    pub fn new(content: String) -> Self {
1007        Self { content }
1008    }
1009}
1010
1011/*
1012-> Computed styles
1013-> Layout
1014-----> Needs to happen only when styles are computed
1015*/
1016
1017// type DomRefCell<T> = RefCell<T>;
1018
1019// pub struct DomData {
1020//     // ... we can probs just get away with using the html5ever types directly. basically just using the servo dom, but without the bindings
1021//     local_name: html5ever::LocalName,
1022//     tag_name: html5ever::QualName,
1023//     namespace: html5ever::Namespace,
1024//     prefix: DomRefCell<Option<html5ever::Prefix>>,
1025//     attrs: DomRefCell<Vec<Attr>>,
1026//     // attrs: DomRefCell<Vec<Dom<Attr>>>,
1027//     id_attribute: DomRefCell<Option<Atom>>,
1028//     is: DomRefCell<Option<LocalName>>,
1029//     // style_attribute: DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>>,
1030//     // attr_list: MutNullableDom<NamedNodeMap>,
1031//     // class_list: MutNullableDom<DOMTokenList>,
1032//     state: Cell<ElementState>,
1033// }
1034
1035impl Node {
1036    pub fn tree(&self) -> &crate::NodeTree {
1037        unsafe { &*self.tree }
1038    }
1039
1040    #[track_caller]
1041    pub fn with(&self, id: NodeId) -> &Node {
1042        self.tree().get(id).unwrap()
1043    }
1044
1045    pub fn print_tree(&self, level: usize) {
1046        println!(
1047            "{} {} {:?} {} {:?}",
1048            "  ".repeat(level),
1049            self.id,
1050            self.parent,
1051            self.node_debug_str().replace('\n', ""),
1052            self.children
1053        );
1054        // println!("{} {:?}", "  ".repeat(level), self.children);
1055        for child_id in self.children.iter() {
1056            let child = self.with(*child_id);
1057            child.print_tree(level + 1)
1058        }
1059    }
1060
1061    // Get the index of the current node in the parents child list
1062    pub fn index_of_child(&self, child_id: NodeId) -> Option<usize> {
1063        self.children.iter().position(|id| *id == child_id)
1064    }
1065
1066    // Get the index of the current node in the parents child list
1067    pub fn child_index(&self) -> Option<usize> {
1068        self.tree()[self.parent?]
1069            .children
1070            .iter()
1071            .position(|id| *id == self.id)
1072    }
1073
1074    // Get the nth node in the parents child list
1075    pub fn forward(&self, n: usize) -> Option<&Node> {
1076        let child_idx = self.child_index().unwrap_or(0);
1077        self.tree()[self.parent?]
1078            .children
1079            .get(child_idx + n)
1080            .map(|id| self.with(*id))
1081    }
1082
1083    pub fn backward(&self, n: usize) -> Option<&Node> {
1084        let child_idx = self.child_index().unwrap_or(0);
1085        if child_idx < n {
1086            return None;
1087        }
1088
1089        self.tree()[self.parent?]
1090            .children
1091            .get(child_idx - n)
1092            .map(|id| self.with(*id))
1093    }
1094
1095    pub fn is_element(&self) -> bool {
1096        matches!(self.data, NodeData::Element { .. })
1097    }
1098
1099    pub fn is_anonymous(&self) -> bool {
1100        matches!(self.data, NodeData::AnonymousBlock { .. })
1101    }
1102
1103    pub fn is_shadow_root(&self) -> bool {
1104        matches!(self.data, NodeData::ShadowRoot { .. })
1105    }
1106
1107    pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
1108        self.data.shadow_root_data()
1109    }
1110
1111    pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
1112        self.data.shadow_root_data_mut()
1113    }
1114
1115    /// If this node is a shadow host (i.e. has an attached shadow root),
1116    /// returns the node id of its shadow root.
1117    pub fn shadow_root_id(&self) -> Option<NodeId> {
1118        self.element_data().and_then(|el| el.shadow_root)
1119    }
1120
1121    /// The children to use for layout and painting. For shadow hosts and
1122    /// `<slot>` elements this is the "flattened tree" children; for all other
1123    /// nodes it is the regular DOM [`children`](Self::children).
1124    #[cfg(feature = "shadow-dom")]
1125    pub fn layout_dom_children(&self) -> &[NodeId] {
1126        match &self.flattened_children {
1127            Some(children) => children,
1128            None => &self.children,
1129        }
1130    }
1131
1132    /// The children to use for layout and painting.
1133    #[cfg(not(feature = "shadow-dom"))]
1134    #[inline(always)]
1135    pub fn layout_dom_children(&self) -> &[NodeId] {
1136        &self.children
1137    }
1138
1139    pub fn is_text_node(&self) -> bool {
1140        matches!(self.data, NodeData::Text { .. })
1141    }
1142
1143    pub fn element_data(&self) -> Option<&ElementData> {
1144        match self.data {
1145            NodeData::Element(ref data) => Some(data),
1146            NodeData::AnonymousBlock(ref data) => Some(data),
1147            _ => None,
1148        }
1149    }
1150
1151    pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
1152        match self.data {
1153            NodeData::Element(ref mut data) => Some(data),
1154            NodeData::AnonymousBlock(ref mut data) => Some(data),
1155            _ => None,
1156        }
1157    }
1158
1159    pub fn text_data(&self) -> Option<&TextNodeData> {
1160        match self.data {
1161            NodeData::Text(ref data) => Some(data),
1162            _ => None,
1163        }
1164    }
1165
1166    pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
1167        match self.data {
1168            NodeData::Text(ref mut data) => Some(data),
1169            _ => None,
1170        }
1171    }
1172
1173    pub fn node_debug_str(&self) -> String {
1174        let mut s = String::new();
1175
1176        match &self.data {
1177            NodeData::Document(_) => write!(s, "DOCUMENT"),
1178            NodeData::DocumentFragment => write!(s, "FRAGMENT"),
1179            // NodeData::Doctype { name, .. } => write!(s, "DOCTYPE {name}"),
1180            NodeData::Text(data) => {
1181                let bytes = data.content.as_bytes();
1182                write!(
1183                    s,
1184                    "TEXT {}",
1185                    std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
1186                        .unwrap_or("INVALID UTF8")
1187                )
1188            }
1189            NodeData::Comment { .. } => write!(s, "COMMENT"),
1190            NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
1191            NodeData::ShadowRoot(data) => write!(s, "#shadow-root ({:?})", data.mode),
1192            NodeData::Element(data) => {
1193                let name = &data.name;
1194                let class = self.attr(local_name!("class")).unwrap_or("");
1195                let id = self.attr(local_name!("id")).unwrap_or("");
1196                let display = self.display_constructed_as().to_css_string();
1197                write!(s, "<{}", name.local).unwrap();
1198                if !id.is_empty() {
1199                    write!(s, " #{id}").unwrap();
1200                }
1201                if !class.is_empty() {
1202                    if class.contains(' ') {
1203                        write!(s, " class=\"{class}\"").unwrap()
1204                    } else {
1205                        write!(s, " .{class}").unwrap()
1206                    }
1207                }
1208                write!(s, "> ({display})")
1209            } // NodeData::ProcessingInstruction { .. } => write!(s, "ProcessingInstruction"),
1210        }
1211        .unwrap();
1212        s
1213    }
1214
1215    /// Renders the HTML of this node and all its children as a `String` without extra whitespace.
1216    ///
1217    /// Example output:
1218    ///
1219    /// ```text
1220    /// <html><head /><body><main id="main"><div class="arbitrary-class" /></main></body></html>
1221    /// ```
1222    pub fn outer_html(&self) -> String {
1223        let mut output = String::new();
1224        self.write_outer_html(&mut output);
1225        output
1226    }
1227
1228    /// Renders the HTML of this node and all its children as a `String` with whitespace for human
1229    /// readability.
1230    ///
1231    /// Example output:
1232    ///
1233    /// ```text
1234    /// <html>
1235    ///   <head />
1236    ///   <body>
1237    ///     <main id="main">
1238    ///       <div class="arbitrary-class" />
1239    ///     </main>
1240    ///   </body>
1241    /// </html>
1242    /// ```
1243    pub fn outer_html_pretty(&self) -> String {
1244        let mut output = String::new();
1245        self.write_outer_html_pretty(&mut output);
1246        output
1247    }
1248
1249    pub fn write_outer_html(&self, writer: &mut String) {
1250        self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, None);
1251    }
1252
1253    #[cfg(feature = "svg")]
1254    pub(crate) fn write_outer_html_with_current_color(
1255        &self,
1256        writer: &mut String,
1257        current_color: &str,
1258    ) {
1259        self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, Some(current_color));
1260    }
1261
1262    pub fn write_outer_html_pretty(&self, writer: &mut String) {
1263        self.write_outer_html_in_style(writer, OutputStyle::Pretty, 0, None);
1264    }
1265
1266    fn write_outer_html_in_style(
1267        &self,
1268        writer: &mut String,
1269        style: OutputStyle,
1270        nesting: usize,
1271        current_color_override: Option<&str>,
1272    ) {
1273        const INDENT: &str = "  ";
1274        let has_children = !self.children.is_empty();
1275        let computed_current_color = || {
1276            self.primary_styles()
1277                .map(|style| style.clone_color())
1278                .map(|color| crate::util::absolute_color_to_svg_css(&color))
1279        };
1280        let current_color = current_color_override
1281            .map(ToOwned::to_owned)
1282            .or_else(computed_current_color);
1283
1284        match &self.data {
1285            NodeData::Document(_) => {}
1286            NodeData::Comment { .. } => {}
1287            NodeData::AnonymousBlock(_) => {}
1288            NodeData::ShadowRoot(_) => {}
1289            // A fragment serialises as nothing of its own: the spec says its
1290            // children are what gets inserted, and it is never in a document
1291            // to be serialised from in the first place.
1292            NodeData::DocumentFragment => {}
1293            // NodeData::Doctype { name, .. } => write!(s, "DOCTYPE {name}"),
1294            NodeData::Text(data) => {
1295                if matches!(style, OutputStyle::Pretty) {
1296                    for _ in 0..nesting {
1297                        writer.push_str(INDENT);
1298                    }
1299                }
1300                writer.push_str(data.content.as_str());
1301                if matches!(style, OutputStyle::Pretty) {
1302                    writer.push('\n');
1303                }
1304            }
1305            NodeData::Element(data) => {
1306                if matches!(style, OutputStyle::Pretty) {
1307                    for _ in 0..nesting {
1308                        writer.push_str(INDENT);
1309                    }
1310                }
1311                writer.push('<');
1312                writer.push_str(&data.name.local);
1313
1314                for attr in data.attrs() {
1315                    writer.push(' ');
1316                    writer.push_str(&attr.name.local);
1317                    writer.push_str("=\"");
1318                    #[allow(clippy::unnecessary_unwrap)] // Convert to if-let chain once stabilised
1319                    if current_color.is_some() && attr.value.contains("currentColor") {
1320                        let value = attr
1321                            .value
1322                            .replace("currentColor", current_color.as_ref().unwrap());
1323                        encode_quoted_attribute_to_string(&value, writer);
1324                    } else {
1325                        encode_quoted_attribute_to_string(&attr.value, writer);
1326                    }
1327                    writer.push('"');
1328                }
1329                if !has_children {
1330                    writer.push_str(" /");
1331                }
1332                writer.push('>');
1333                if matches!(style, OutputStyle::Pretty) {
1334                    writer.push('\n');
1335                }
1336
1337                if has_children {
1338                    for &child_id in &self.children {
1339                        self.tree()[child_id].write_outer_html_in_style(
1340                            writer,
1341                            style,
1342                            nesting + 1,
1343                            current_color_override,
1344                        );
1345                    }
1346
1347                    if matches!(style, OutputStyle::Pretty) {
1348                        for _ in 0..nesting {
1349                            writer.push_str(INDENT);
1350                        }
1351                    }
1352                    writer.push_str("</");
1353                    writer.push_str(&data.name.local);
1354                    writer.push('>');
1355                    if matches!(style, OutputStyle::Pretty) {
1356                        writer.push('\n');
1357                    }
1358                }
1359            }
1360        }
1361    }
1362
1363    pub fn attrs(&self) -> Option<&[Attribute]> {
1364        Some(&self.element_data()?.attrs)
1365    }
1366
1367    pub fn attr(&self, name: LocalName) -> Option<&str> {
1368        let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
1369        Some(&attr.value)
1370    }
1371
1372    pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
1373        self.stylo_element_data_opt()
1374            .and_then(|stylo| stylo.primary_styles())
1375    }
1376
1377    pub fn text_content(&self) -> String {
1378        let mut out = String::new();
1379        self.write_text_content(&mut out);
1380        out
1381    }
1382
1383    /// Write this subtree's text content into any [`std::fmt::Write`] sink.
1384    ///
1385    /// Public and generic so that a caller which does not want a `String` does
1386    /// not have to fork this traversal to avoid one. `blitz-dom-api`'s
1387    /// buffer-writing reader passes a sink that counts, and then one that fills
1388    /// a caller-supplied slice; a private copy of this walk in that crate would
1389    /// silently disagree with this one the first time a `NodeData` variant is
1390    /// added here.
1391    ///
1392    /// The sinks callers pass do not fail, and `String`'s never has, so nothing
1393    /// in this crate inspects the `Result`. It is kept in the signature because
1394    /// it is `fmt::Write`'s, not because there is an error to handle.
1395    pub fn write_text_content<W: Write>(&self, out: &mut W) {
1396        match &self.data {
1397            NodeData::Text(data) => {
1398                let _ = out.write_str(&data.content);
1399            }
1400            NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
1401                for child_id in self.children.iter() {
1402                    self.with(*child_id).write_text_content(out);
1403                }
1404            }
1405            _ => {}
1406        }
1407    }
1408
1409    pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
1410        if let NodeData::Element(ref mut elem_data) = self.data {
1411            if let Some(guard) = elem_data.guard.clone() {
1412                elem_data.flush_style_attribute(&guard, url_extra_data);
1413            }
1414        }
1415    }
1416
1417    pub fn order(&self) -> i32 {
1418        self.primary_styles()
1419            .map(|s| match s.pseudo() {
1420                Some(PseudoElement::Before) => i32::MIN,
1421                Some(PseudoElement::After) => i32::MAX,
1422                _ => s.clone_order(),
1423            })
1424            .unwrap_or(0)
1425    }
1426
1427    pub fn z_index(&self) -> i32 {
1428        self.primary_styles()
1429            .map(|s| s.clone_z_index().integer_or(0))
1430            .unwrap_or(0)
1431    }
1432
1433    // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context#features_creating_stacking_contexts
1434    pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
1435        let Some(style) = self.primary_styles() else {
1436            return false;
1437        };
1438
1439        let position = style.clone_position();
1440        let has_z_index = !style.clone_z_index().is_auto();
1441
1442        if style.clone_opacity() != 1.0 {
1443            return true;
1444        }
1445
1446        let position_based = match position {
1447            Position::Fixed | Position::Sticky => true,
1448            Position::Relative | Position::Absolute => has_z_index,
1449            Position::Static => has_z_index && is_flex_or_grid_item,
1450        };
1451        if position_based {
1452            return true;
1453        }
1454
1455        // This runs while the stacking-context tree is built, before
1456        // `resolve_transforms` populates the cached device-space matrix. Using
1457        // that cache here makes the first frame treat a transformed node as an
1458        // ordinary box, hoist its painted children into the parent, and only
1459        // repair the tree after the first hover/restyle. The computed CSS value
1460        // is already available and is the source of truth for whether the node
1461        // establishes a stacking context.
1462        let box_styles = style.get_box();
1463        if !box_styles.transform.0.is_empty()
1464            || !matches!(box_styles.translate, Translate::None)
1465            || !matches!(box_styles.rotate, Rotate::None)
1466            || !matches!(box_styles.scale, Scale::None)
1467        {
1468            return true;
1469        }
1470
1471        // `isolation: isolate` exists precisely to create a stacking context
1472        // without any other visual effect. Ignoring it lets a negative z-index
1473        // descendant escape to an ancestor context, where it is painted before
1474        // (and so underneath) the backgrounds of the boxes in between.
1475        if box_styles.isolation == Isolation::Isolate {
1476            return true;
1477        }
1478
1479        // TODO: mix-blend-mode
1480        // TODO: filter
1481        // TODO: clip-path
1482        // TODO: mask
1483        // TODO: contain
1484
1485        false
1486    }
1487
1488    /// Takes an (x, y) position (relative to the *parent's* top-left corner) and returns:
1489    ///    - None if the position is outside of this node's bounds
1490    ///    - Some(HitResult) if the position is within the node but doesn't match any children
1491    ///    - The result of recursively calling child.hit() on the the child element that is
1492    ///      positioned at that position if there is one.
1493    ///
1494    /// TODO: z-index
1495    /// (If multiple children are positioned at the position then a random one will be recursed into)
1496    pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
1497        self.hit_inner(x, y, scale, &mut None)
1498    }
1499
1500    /// [`hit`](Self::hit), also resolving the innermost overlay scrollbar
1501    /// thumb under the point into `scrollbar` during the same descent (so
1502    /// thumb hit-testing shares the exact coordinate handling — transforms
1503    /// included — of every other hit test).
1504    pub(crate) fn hit_inner(
1505        &self,
1506        x: f32,
1507        y: f32,
1508        scale: f64,
1509        scrollbar: &mut Option<crate::node::ScrollbarRef>,
1510    ) -> Option<HitResult> {
1511        use style::computed_values::pointer_events::T as PointerEvents;
1512        use style::computed_values::visibility::T as Visibility;
1513
1514        // A hidden subtree takes no hits.
1515        //
1516        // This never needed saying while hiding a pane destroyed its boxes: a
1517        // hidden subtree had nothing to test against. It keeps its boxes now,
1518        // and their `final_layout` is whatever it was when the pane was last
1519        // visible — full size, in place, over the tab in front. A retained tab
1520        // measured 331 of its 370 elements still carrying live geometry after
1521        // being hidden, and every one of them was a click target.
1522        if matches!(self.style().display, taffy::Display::None) {
1523            return None;
1524        }
1525
1526        // Don't hit on visbility:hidden elements
1527        if let Some(style) = self.primary_styles() {
1528            if matches!(
1529                style.clone_visibility(),
1530                Visibility::Hidden | Visibility::Collapse
1531            ) {
1532                return None;
1533            }
1534        }
1535
1536        // pointer-events:none makes this element transparent to hits, but its
1537        // descendants are still tested (one may restore pointer-events:auto).
1538        let pointer_events_none = self
1539            .primary_styles()
1540            .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1541
1542        let mut x = x - self.final_layout().location.x + self.scroll_offset().x as f32;
1543        let mut y = y - self.final_layout().location.y + self.scroll_offset().y as f32;
1544
1545        if let Some(t) = *self.transform() {
1546            let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
1547            x = (p.x / scale) as f32;
1548            y = (p.y / scale) as f32;
1549        }
1550
1551        let size = self.final_layout().size;
1552        let matches_self = !(x < 0.0
1553            || x > size.width + self.scroll_offset().x as f32
1554            || y < 0.0
1555            || y > size.height + self.scroll_offset().y as f32);
1556
1557        let content_size = self.final_layout().content_size;
1558        let matches_content = !(x < 0.0
1559            || x > content_size.width + self.scroll_offset().x as f32
1560            || y < 0.0
1561            || y > content_size.height + self.scroll_offset().y as f32);
1562
1563        let matches_hoisted_content = match &self.stacking_context {
1564            Some(sc) => {
1565                let content_area = sc.content_area;
1566                x >= content_area.left + self.scroll_offset().x as f32
1567                    && x <= content_area.right + self.scroll_offset().x as f32
1568                    && y >= content_area.top + self.scroll_offset().y as f32
1569                    && y <= content_area.bottom + self.scroll_offset().y as f32
1570            }
1571            None => false,
1572        };
1573
1574        // `scrollable_overflow` is stored in device (scaled) pixels, whereas the
1575        // coordinates here are in CSS pixels, so unscale it before comparing.
1576        let overflow = *self.scrollable_overflow();
1577
1578        let matches_overflow = x >= (overflow.x0 / scale) as f32
1579            && x <= (overflow.x1 / scale) as f32
1580            && y >= (overflow.y0 / scale) as f32
1581            && y <= (overflow.y1 / scale) as f32;
1582
1583        if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
1584            return None;
1585        }
1586
1587        // Descendants overwrite, so the innermost scroll container's thumb
1588        // wins. Thumb coords are border-box relative (unscrolled).
1589        if matches_self
1590            && let Some(sb) = self.scrollbar_at_local(
1591                (x - self.scroll_offset().x as f32) as f64,
1592                (y - self.scroll_offset().y as f32) as f64,
1593            )
1594        {
1595            *scrollbar = Some(sb);
1596        }
1597
1598        if self.flags.is_inline_root() {
1599            let content_box_offset = taffy::Point {
1600                x: self.final_layout().padding.left + self.final_layout().border.left,
1601                y: self.final_layout().padding.top + self.final_layout().border.top,
1602            };
1603            x -= content_box_offset.x;
1604            y -= content_box_offset.y;
1605        }
1606
1607        // Positive z_index hoisted children
1608        if matches_hoisted_content {
1609            if let Some(hoisted) = &self.stacking_context {
1610                for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1611                    let x = x - hoisted_child.position.x;
1612                    let y = y - hoisted_child.position.y;
1613                    if let Some(hit) = self
1614                        .with(hoisted_child.node_id)
1615                        .hit_inner(x, y, scale, scrollbar)
1616                    {
1617                        return Some(hit);
1618                    }
1619                }
1620            }
1621        }
1622
1623        // Call `.hit()` on each child in turn. If any return `Some` then return that value. Else return `Some(self.id).
1624        for child_id in self.paint_children.borrow().iter().flatten().rev() {
1625            if let Some(hit) = self.with(*child_id).hit_inner(x, y, scale, scrollbar) {
1626                return Some(hit);
1627            }
1628        }
1629
1630        // Negative z_index hoisted children
1631        if matches_hoisted_content {
1632            if let Some(hoisted) = &self.stacking_context {
1633                for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1634                    let x = x - hoisted_child.position.x;
1635                    let y = y - hoisted_child.position.y;
1636                    if let Some(hit) = self
1637                        .with(hoisted_child.node_id)
1638                        .hit_inner(x, y, scale, scrollbar)
1639                    {
1640                        return Some(hit);
1641                    }
1642                }
1643            }
1644        }
1645
1646        // Inline children
1647        if self.flags.is_inline_root() {
1648            let element_data = &self.element_data().unwrap();
1649            if let Some(ild) = element_data.inline_layout_data.as_ref() {
1650                let layout = &ild.layout;
1651                let scale = layout.scale();
1652
1653                if let Some((cluster, _side)) =
1654                    Cluster::from_point_exact(layout, x * scale, y * scale)
1655                {
1656                    let style_index = cluster.glyphs().next()?.style_index();
1657                    let node_id = layout.styles()[style_index].brush.id;
1658                    let text_pointer_events_none = self
1659                        .with(node_id)
1660                        .primary_styles()
1661                        .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1662                    if !text_pointer_events_none {
1663                        return Some(HitResult {
1664                            node_id,
1665                            x,
1666                            y,
1667                            is_text: true,
1668                        });
1669                    }
1670                }
1671            }
1672        }
1673
1674        // Self (this node)
1675        if matches_self && !pointer_events_none {
1676            return Some(HitResult {
1677                node_id: self.id,
1678                x,
1679                y,
1680                is_text: false,
1681            });
1682        }
1683
1684        None
1685    }
1686
1687    /// Find the inline root ancestor of this node (or self if this is an inline root).
1688    /// Returns None if no inline root ancestor exists.
1689    pub fn inline_root_ancestor(&self) -> Option<&Node> {
1690        let mut node = self;
1691        loop {
1692            if node.flags.is_inline_root() {
1693                return Some(node);
1694            }
1695            let id = node.layout_parent.get()?;
1696            node = self.with(id);
1697        }
1698    }
1699
1700    /// Get the text byte offset at a given point, using coordinates already transformed
1701    /// to be relative to this inline root's content box.
1702    /// Returns Some(byte_offset) if the point hits text, None otherwise.
1703    pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1704        if !self.flags.is_inline_root() {
1705            return None;
1706        }
1707
1708        let element_data = self.element_data()?;
1709        let inline_layout = element_data.inline_layout_data.as_ref()?;
1710        let layout = &inline_layout.layout;
1711        let scale = layout.scale();
1712
1713        // Use Parley's cluster hit testing (from_point is more forgiving than from_point_exact)
1714        let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1715
1716        // Determine byte offset based on which side of the cluster was clicked
1717        // For LTR text: left side = start of cluster, right side = end of cluster
1718        // For RTL text: left side = end of cluster, right side = start of cluster
1719        // Also, explicit line breaks should always use start to avoid cursor appearing on next line
1720        let is_leading = side == ClusterSide::Left;
1721        let offset = if cluster.is_rtl() {
1722            if is_leading {
1723                cluster.text_range().end
1724            } else {
1725                cluster.text_range().start
1726            }
1727        } else {
1728            // LTR text
1729            if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1730                cluster.text_range().start
1731            } else {
1732                cluster.text_range().end
1733            }
1734        };
1735
1736        Some(offset)
1737    }
1738
1739    /// The byte range of the word or line at a point, for a multi-click
1740    /// selection.
1741    ///
1742    /// See [`TextGranularity`] for which click count maps to which unit. Coordinates are relative to this inline root's content box,
1743    /// as for [`text_offset_at_point`](Self::text_offset_at_point).
1744    ///
1745    /// Parley owns the boundary rules (it is what an `<input>` already selects
1746    /// with), so this asks it rather than scanning for spaces: word breaks are
1747    /// a Unicode segmentation question, not a whitespace one.
1748    pub fn text_range_at_point(
1749        &self,
1750        x: f32,
1751        y: f32,
1752        granularity: TextGranularity,
1753    ) -> Option<Range<usize>> {
1754        if !self.flags.is_inline_root() {
1755            return None;
1756        }
1757
1758        let element_data = self.element_data()?;
1759        let inline_layout = element_data.inline_layout_data.as_ref()?;
1760        let layout = &inline_layout.layout;
1761        let scale = layout.scale();
1762        let (x, y) = (x * scale, y * scale);
1763
1764        // Bail when the point misses the text entirely: `Selection` answers an
1765        // out-of-range point with a collapsed cursor at the end of the text,
1766        // which would read as "selected nothing at the very end" rather than
1767        // as a miss.
1768        Cluster::from_point(layout, x, y)?;
1769
1770        let selection = match granularity {
1771            TextGranularity::Word => Selection::word_from_point(layout, x, y),
1772            // The hard line, so a soft-wrapped paragraph selects as the whole
1773            // paragraph. That is what a triple click does elsewhere, and it is
1774            // what `select_hard_line_at_point` gives a text input.
1775            TextGranularity::Line => Selection::hard_line_from_point(layout, x, y),
1776        };
1777
1778        let range = selection.text_range();
1779        if range.is_empty() { None } else { Some(range) }
1780    }
1781
1782    /// Computes the Document-relative coordinates of the `Node`
1783    pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1784        // A scroll offset moves this node's descendants, not its own border
1785        // box. Parent recursion applies each ancestor offset to the child.
1786        let x = x + self.final_layout().location.x;
1787        let y = y + self.final_layout().location.y;
1788
1789        // Recurse up the layout hierarchy
1790        self.layout_parent
1791            .get()
1792            .and_then(|id| self.tree().get(id))
1793            .map(|parent| {
1794                parent.absolute_position(
1795                    x - parent.scroll_offset().x as f32,
1796                    y - parent.scroll_offset().y as f32,
1797                )
1798            })
1799            .unwrap_or(crate::util::Point { x, y })
1800    }
1801
1802    /// Whether this node can act as an [`offset_parent`](Self::offset_parent): a positioned
1803    /// element, or one of the elements that always qualify (`body`, `td`, `th`).
1804    fn is_offset_parent(&self) -> bool {
1805        let Some(styles) = self.primary_styles() else {
1806            return false;
1807        };
1808        if styles.get_box().position != Position::Static {
1809            return true;
1810        }
1811        self.data.is_element_with_tag_name(&local_name!("body"))
1812            || self.data.is_element_with_tag_name(&local_name!("td"))
1813            || self.data.is_element_with_tag_name(&local_name!("th"))
1814    }
1815
1816    /// The nearest layout ancestor that is an offset parent, as in CSSOM View's
1817    /// `offsetParent`.
1818    ///
1819    /// The predicate is `is_offset_parent`, which is private, so this is not a
1820    /// link: rustdoc rejects a public item linking to a private one under
1821    /// `-D warnings`, and the alternative is publishing a helper nothing else
1822    /// needs.
1823    pub fn offset_parent(&self) -> Option<&Node> {
1824        let mut node = self;
1825        loop {
1826            node = self.with(node.layout_parent.get()?);
1827            if node.is_offset_parent() {
1828                return Some(node);
1829            }
1830        }
1831    }
1832
1833    /// CSSOM View's `offsetLeft`/`offsetTop`: the offset of this node's border box from the
1834    /// padding edge of its [`offset_parent`](Self::offset_parent).
1835    pub fn offset_top_left(&self) -> crate::util::Point<f32> {
1836        let mut x = 0.0;
1837        let mut y = 0.0;
1838        let mut current = self;
1839        loop {
1840            let layout = current.final_layout();
1841            x += layout.location.x;
1842            y += layout.location.y;
1843
1844            let Some(parent_id) = current.layout_parent.get() else {
1845                break;
1846            };
1847            let parent = self.with(parent_id);
1848            if parent.is_offset_parent() {
1849                let border = parent.final_layout().border;
1850                x -= border.left;
1851                y -= border.top;
1852                break;
1853            }
1854            current = parent;
1855        }
1856        crate::util::Point { x, y }
1857    }
1858
1859    /// Creates a synthetic click event
1860    pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1861        DomEventData::Click(self.synthetic_click_event_data(mods))
1862    }
1863
1864    pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent {
1865        let absolute_position = self.absolute_position(0.0, 0.0);
1866        let x = absolute_position.x + (self.final_layout().size.width / 2.0);
1867        let y = absolute_position.y + (self.final_layout().size.height / 2.0);
1868
1869        BlitzPointerEvent {
1870            id: BlitzPointerId::Mouse,
1871            is_primary: true,
1872            coords: PointerCoords {
1873                page_x: x,
1874                page_y: y,
1875
1876                // TODO: should these be different?
1877                screen_x: x,
1878                screen_y: y,
1879                client_x: x,
1880                client_y: y,
1881            },
1882            mods,
1883            button: Default::default(),
1884            buttons: Default::default(),
1885            details: Default::default(),
1886            element: Default::default(),
1887            active_pointers: Default::default(),
1888        }
1889    }
1890}
1891
1892/// It might be wrong to expose this since what does *equality* mean outside the dom?
1893impl PartialEq for Node {
1894    fn eq(&self, other: &Self) -> bool {
1895        self.id == other.id
1896    }
1897}
1898
1899impl Eq for Node {}
1900
1901impl std::fmt::Debug for Node {
1902    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1903        // FIXME: update to reflect changes to fields
1904        f.debug_struct("NodeData")
1905            .field("parent", &self.parent)
1906            .field("id", &self.id)
1907            .field("is_inline_root", &self.flags.is_inline_root())
1908            .field("children", &self.children)
1909            .field("layout_children", &self.layout_children.borrow())
1910            // .field("style", &self.style)
1911            .field("node", &self.data)
1912            .field("stylo_element_data", &self.stylo_element_data_opt())
1913            // .field("unrounded_layout", &self.unrounded_layout)
1914            // .field("final_layout", &self.final_layout)
1915            .finish()
1916    }
1917}
1918
1919#[cfg(test)]
1920mod test {
1921    use style_dom::ElementState;
1922
1923    use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1924
1925    #[test]
1926    fn absolute_position_tolerates_a_detached_layout_parent() {
1927        let mut document = BaseDocument::new(DocumentConfig::default());
1928        let parent = document.create_node(NodeData::Element(Box::new(ElementData::new(
1929            qual_name!("section"),
1930            vec![],
1931        ))));
1932        let child = document.create_node(NodeData::Element(Box::new(ElementData::new(
1933            qual_name!("button"),
1934            vec![],
1935        ))));
1936        document
1937            .get_node(child)
1938            .unwrap()
1939            .layout_parent
1940            .set(Some(parent));
1941
1942        // An event handler may detach its own ancestor before the renderer's
1943        // default-action tail finishes computing event-relative coordinates.
1944        // The child object can still be alive through that dispatch even though
1945        // its recorded layout parent has already left the slot map.
1946        document.remove_node_from_tree(parent);
1947
1948        assert_eq!(
1949            document
1950                .get_node(child)
1951                .unwrap()
1952                .absolute_position(4.0, 7.0),
1953            crate::util::Point { x: 4.0, y: 7.0 },
1954        );
1955    }
1956
1957    #[test]
1958    fn create_node_with_disabled_attr() {
1959        let mut document = BaseDocument::new(DocumentConfig::default());
1960        let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1961            qual_name!("button"),
1962            vec![Attribute {
1963                name: qual_name!("disabled"),
1964                value: "".into(),
1965            }],
1966        ))));
1967        let node = document.get_node(node).unwrap();
1968
1969        assert!(
1970            node.element_state().contains(ElementState::DISABLED),
1971            "form node is disabled"
1972        );
1973        assert!(
1974            !node.element_state().contains(ElementState::ENABLED),
1975            "form node is not enabled"
1976        );
1977    }
1978
1979    #[test]
1980    fn ignore_disabled_attr_content() {
1981        let mut document = BaseDocument::new(DocumentConfig::default());
1982        let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1983            qual_name!("button"),
1984            vec![Attribute {
1985                name: qual_name!("disabled"),
1986                value: "false".into(),
1987            }],
1988        ))));
1989        let node = document.get_node(node).unwrap();
1990
1991        assert!(
1992            node.element_state().contains(ElementState::DISABLED),
1993            "form node is disabled"
1994        );
1995        assert!(
1996            !node.element_state().contains(ElementState::ENABLED),
1997            "form node is not enabled"
1998        );
1999    }
2000
2001    #[test]
2002    fn create_node_with_ignored_disable() {
2003        let mut document = BaseDocument::new(DocumentConfig::default());
2004        let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2005            qual_name!("a"),
2006            vec![Attribute {
2007                name: qual_name!("disabled"),
2008                value: "".into(),
2009            }],
2010        ))));
2011        let node = document.get_node(node).unwrap();
2012
2013        assert!(
2014            !node.element_state().contains(ElementState::DISABLED),
2015            "Non form node cannot be disabled"
2016        );
2017        assert!(
2018            !node.element_state().contains(ElementState::ENABLED),
2019            "Non form node cannot be enabled"
2020        );
2021    }
2022
2023    #[test]
2024    fn create_empty_enabled_node() {
2025        let mut document = BaseDocument::new(DocumentConfig::default());
2026        let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
2027            qual_name!("button"),
2028            vec![],
2029        ))));
2030        let node = document.get_node(node).unwrap();
2031
2032        assert!(
2033            node.element_state().contains(ElementState::ENABLED),
2034            "Button should be enabled by default"
2035        );
2036    }
2037}