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        Some(vec![
424            ("display", style.clone_display().to_css_string()),
425            ("position", style.clone_position().to_css_string()),
426            ("visibility", style.clone_visibility().to_css_string()),
427            ("opacity", style.clone_opacity().to_css_string()),
428            ("color", style.clone_color().to_css_string()),
429            (
430                "background-color",
431                style.clone_background_color().to_css_string(),
432            ),
433            (
434                "font-size",
435                format!("{}px", style.clone_font_size().computed_size().px()),
436            ),
437            ("font-weight", style.clone_font_weight().to_css_string()),
438            ("font-style", style.clone_font_style().to_css_string()),
439            ("font-family", style.clone_font_family().to_css_string()),
440            ("text-align", style.clone_text_align().to_css_string()),
441            ("overflow-x", style.clone_overflow_x().to_css_string()),
442            ("overflow-y", style.clone_overflow_y().to_css_string()),
443            ("z-index", style.clone_z_index().to_css_string()),
444            ("box-sizing", style.clone_box_sizing().to_css_string()),
445            (
446                "flex-direction",
447                style.clone_flex_direction().to_css_string(),
448            ),
449            (
450                "justify-content",
451                style.clone_justify_content().to_css_string(),
452            ),
453            ("align-items", style.clone_align_items().to_css_string()),
454            // The used values, not the specified ones. A page reading `width`
455            // off `getComputedStyle` is nearly always asking how wide the box
456            // ended up, and `auto` is not an answer it can use.
457            ("width", format!("{}px", layout.size.width)),
458            ("height", format!("{}px", layout.size.height)),
459        ])
460    }
461
462    pub fn diagnostic_computed_style(&self) -> Option<Vec<(&'static str, String)>> {
463        let style = self.primary_styles()?;
464        Some(vec![
465            ("display", style.clone_display().to_css_string()),
466            ("color", style.clone_color().to_css_string()),
467            (
468                "background-color",
469                style.clone_background_color().to_css_string(),
470            ),
471            (
472                "font-size",
473                format!("{}px", style.clone_font_size().computed_size().px()),
474            ),
475            ("width", style.clone_width().to_css_string()),
476        ])
477    }
478
479    /// Whether computed style removes this node from layout.
480    pub fn is_display_none(&self) -> bool {
481        self.display_style()
482            .is_some_and(|display| display.is_none())
483    }
484
485    pub fn is_or_contains_block(&self) -> bool {
486        let style = self.primary_styles();
487        let style = style.as_ref();
488
489        // Ignore out-of-flow items
490        let position = style
491            .map(|s| s.clone_position())
492            .unwrap_or(Position::Relative);
493        let is_in_flow = matches!(
494            position,
495            Position::Static | Position::Relative | Position::Sticky
496        );
497        if !is_in_flow {
498            return false;
499        }
500        // Floated boxes do not break up the inline flow: they participate in the
501        // inline formatting context as out-of-flow inline boxes
502        let is_floating = style
503            .map(|s| s.clone_float().is_floating())
504            .unwrap_or(false);
505        if is_floating {
506            return false;
507        }
508        let display = style
509            .map(|s| s.clone_display())
510            .unwrap_or(StyloDisplay::inline());
511        match display.outside() {
512            DisplayOutside::None => false,
513            DisplayOutside::Block => true,
514            _ => {
515                if display.inside() == DisplayInside::Flow {
516                    self.children
517                        .iter()
518                        .copied()
519                        .any(|child_id| self.tree()[child_id].is_or_contains_block())
520                } else {
521                    false
522                }
523            }
524        }
525    }
526
527    pub fn is_whitespace_node(&self) -> bool {
528        match &self.data {
529            NodeData::Text(data) => data.content.chars().all(|c| c.is_ascii_whitespace()),
530            _ => false,
531        }
532    }
533
534    pub fn is_focussable(&self) -> bool {
535        self.data
536            .downcast_element()
537            .map(|el| el.is_focussable)
538            .unwrap_or(false)
539    }
540
541    pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
542        if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
543            if let Some(mut element_data) = stylo_element_data.get_mut() {
544                element_data.hint.insert(hint);
545            }
546        }
547        // Mark all ancestors as having dirty descendants so the style traversal
548        // will visit this node's subtree
549        self.mark_ancestors_dirty();
550    }
551
552    /// Returns whether this node has any descendants that need restyling.
553    pub fn has_dirty_descendants(&self) -> bool {
554        self.dirty_descendants_flag()
555            .is_some_and(|flag| flag.load(Ordering::Relaxed))
556    }
557
558    /// Sets the dirty_descendants flag on this node.
559    pub fn set_dirty_descendants(&self) {
560        if let Some(flag) = self.dirty_descendants_flag() {
561            flag.store(true, Ordering::Relaxed);
562        }
563    }
564
565    /// Clears the dirty_descendants flag on this node.
566    pub fn unset_dirty_descendants(&self) {
567        if let Some(flag) = self.dirty_descendants_flag() {
568            flag.store(false, Ordering::Relaxed);
569        }
570    }
571
572    /// Set appropriate damage for Stylo when an element's style attribute is updated
573    pub(crate) fn mark_style_attr_updated(&mut self) {
574        if let Some(stylo_element_data) = self.stylo_element_data_opt_mut() {
575            if let Some(mut data) = stylo_element_data.get_mut() {
576                data.hint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
577            }
578        }
579        self.set_dirty_descendants();
580        self.mark_ancestors_dirty();
581    }
582
583    /// Marks all ancestors of this node as having dirty descendants.
584    /// This propagates the dirty flag up the tree so that the style traversal
585    /// knows to visit the subtree containing this node.
586    pub fn mark_ancestors_dirty(&self) {
587        let mut current_id = self.parent;
588        while let Some(parent_id) = current_id {
589            let parent = &self.tree()[parent_id];
590            // If this ancestor already has dirty_descendants set, we can stop
591            // because all further ancestors must also have it set
592            if let Some(flag) = parent.dirty_descendants_flag() {
593                if flag.swap(true, Ordering::Relaxed) {
594                    break;
595                }
596            }
597            current_id = parent.parent;
598        }
599    }
600
601    // pub fn damage_mut(&mut self) -> Option<&mut RestyleDamage> {
602    //     self.stylo_element_data
603    //         .get_mut()
604    //         .map(|mut data: ElementDataMut<'a>| &'a mut data.damage)
605    // }
606
607    pub fn damage(&self) -> Option<RestyleDamage> {
608        self.stylo_element_data_opt()
609            .and_then(|stylo| stylo.get().map(|data| data.damage))
610    }
611
612    pub fn set_damage(&mut self, damage: RestyleDamage) {
613        if let Some(stylo) = self.stylo_element_data_opt_mut() {
614            if let Some(mut data) = stylo.get_mut() {
615                data.damage = damage;
616            }
617        }
618    }
619
620    pub fn insert_damage(&mut self, damage: RestyleDamage) {
621        if let Some(stylo) = self.stylo_element_data_opt_mut() {
622            if let Some(mut data) = stylo.get_mut() {
623                data.damage |= damage;
624            }
625        }
626    }
627
628    pub fn remove_damage(&mut self, damage: RestyleDamage) {
629        if let Some(stylo) = self.stylo_element_data_opt_mut() {
630            if let Some(mut data) = stylo.get_mut() {
631                data.damage.remove(damage);
632            }
633        }
634    }
635
636    pub fn clear_damage_mut(&mut self) {
637        if let Some(stylo) = self.stylo_element_data_opt_mut() {
638            if let Some(mut data) = stylo.get_mut() {
639                data.damage = RestyleDamage::empty();
640            }
641        }
642    }
643
644    pub fn hover(&mut self) {
645        if let Some(data) = self.element_data_mut() {
646            data.element_state.insert(ElementState::HOVER);
647        }
648        self.set_restyle_hint(RestyleHint::restyle_subtree());
649    }
650
651    pub fn unhover(&mut self) {
652        if let Some(data) = self.element_data_mut() {
653            data.element_state.remove(ElementState::HOVER);
654        }
655        self.set_restyle_hint(RestyleHint::restyle_subtree());
656    }
657
658    pub fn is_hovered(&self) -> bool {
659        self.element_data()
660            .is_some_and(|data| data.element_state.contains(ElementState::HOVER))
661    }
662
663    pub fn focus(&mut self, shell_provider: Arc<dyn ShellProvider>) {
664        if let Some(data) = self.element_data_mut() {
665            data.element_state
666                .insert(ElementState::FOCUS | ElementState::FOCUSRING);
667        }
668        self.set_restyle_hint(RestyleHint::restyle_subtree());
669
670        // If focussing a text input, enable IME and set IME area
671        if self
672            .element_data()
673            .and_then(|elem| elem.text_input_data())
674            .is_some()
675        {
676            shell_provider.set_ime_enabled(true);
677            let mut pos = self.absolute_position(0.0, 0.0);
678            pos.x += self.final_layout().content_box_x();
679            pos.y += self.final_layout().content_box_y();
680            let width = self.final_layout().content_box_width();
681            let height = self.final_layout().content_box_height();
682            shell_provider.set_ime_cursor_area(pos.x, pos.y, width, height);
683        }
684    }
685
686    pub fn blur(&mut self, shell_provider: Arc<dyn ShellProvider>) {
687        if let Some(data) = self.element_data_mut() {
688            data.element_state
689                .remove(ElementState::FOCUS | ElementState::FOCUSRING);
690        }
691        self.set_restyle_hint(RestyleHint::restyle_subtree());
692
693        // If blurring a text input, disable IME
694        if self
695            .element_data()
696            .and_then(|elem| elem.text_input_data())
697            .is_some()
698        {
699            shell_provider.set_ime_enabled(false);
700        }
701    }
702
703    pub fn is_focussed(&self) -> bool {
704        self.element_data()
705            .is_some_and(|data| data.element_state.contains(ElementState::FOCUS))
706    }
707
708    pub fn active(&mut self) {
709        if let Some(data) = self.element_data_mut() {
710            data.element_state.insert(ElementState::ACTIVE);
711        }
712        self.set_restyle_hint(RestyleHint::restyle_subtree());
713    }
714
715    pub fn unactive(&mut self) {
716        if let Some(data) = self.element_data_mut() {
717            data.element_state.remove(ElementState::ACTIVE);
718        }
719        self.set_restyle_hint(RestyleHint::restyle_subtree());
720    }
721
722    pub fn is_active(&self) -> bool {
723        self.element_data()
724            .is_some_and(|data| data.element_state.contains(ElementState::ACTIVE))
725    }
726
727    // Marks the node as disabled if it can be.
728    // It does not disable any children which should be disabled as well (relevant for the `select` element).
729    pub fn disable(&mut self) {
730        if let Some(data) = self.element_data_mut() {
731            if data.can_be_disabled() {
732                data.element_state.insert(ElementState::DISABLED);
733                data.element_state.remove(ElementState::ENABLED);
734            }
735        }
736        self.set_restyle_hint(RestyleHint::restyle_subtree());
737    }
738
739    // Marks the node as enabled if it can be.
740    // It does not enable any children which should be enabled as well (relevant for the `select` element).
741    pub fn enable(&mut self) {
742        if let Some(data) = self.element_data_mut() {
743            if data.can_be_disabled() {
744                data.element_state.insert(ElementState::ENABLED);
745                data.element_state.remove(ElementState::DISABLED);
746            }
747        }
748        self.set_restyle_hint(RestyleHint::restyle_subtree());
749    }
750
751    pub fn subdoc(&self) -> Option<&dyn Document> {
752        self.element_data().and_then(|el| el.sub_doc_data())
753    }
754
755    pub fn subdoc_mut(&mut self) -> Option<&mut dyn Document> {
756        self.element_data_mut().and_then(|el| el.sub_doc_data_mut())
757    }
758
759    pub fn text_input_v_centering_offset(&self, scale: f64) -> f64 {
760        // For single-line inputs, add an offset to vertically center the text input layout
761        // within the content box of it's node.
762        if let Some(input_data) = self
763            .data
764            .downcast_element()
765            .and_then(|el| el.text_input_data())
766        {
767            if !input_data.is_multiline {
768                let content_box_height = self.final_layout().content_box_height();
769                let input_height = input_data.editor.try_layout().unwrap().height() / scale as f32;
770                let y_offset = ((content_box_height - input_height) / 2.0).max(0.0);
771
772                return y_offset as f64;
773            }
774        }
775
776        0.0
777    }
778}
779
780#[derive(Debug, Clone, Copy, PartialEq)]
781pub enum NodeKind {
782    Document,
783    Element,
784    AnonymousBlock,
785    Text,
786    Comment,
787    DocumentFragment,
788    ShadowRoot,
789}
790
791/// How much text one click selects.
792///
793/// Click count decides: a second click takes the word, a third takes the line,
794/// matching what a text input does and what every other platform does with the
795/// same gesture.
796#[derive(Debug, Clone, Copy, PartialEq, Eq)]
797pub enum TextGranularity {
798    /// The word under the pointer, by Unicode word segmentation.
799    Word,
800    /// The whole hard line, so a soft-wrapped paragraph selects entire.
801    Line,
802}
803
804impl TextGranularity {
805    /// The granularity a click of this count selects, or `None` for a first
806    /// click, which places a caret rather than selecting anything.
807    pub fn from_click_count(count: u16) -> Option<Self> {
808        match count {
809            0 | 1 => None,
810            2 => Some(Self::Word),
811            _ => Some(Self::Line),
812        }
813    }
814}
815
816/// The encapsulation mode of a shadow root.
817///
818/// Mirrors the `ShadowRootMode` enum from the DOM specification.
819#[derive(Debug, Clone, Copy, PartialEq, Eq)]
820pub enum ShadowRootMode {
821    /// Elements of the shadow root are accessible from JavaScript outside the
822    /// root (e.g. via `Element.shadowRoot`).
823    Open,
824    /// Elements of the shadow root are not accessible from JavaScript outside
825    /// the root.
826    Closed,
827}
828
829/// Data associated with a [`NodeData::ShadowRoot`] node.
830///
831/// A shadow root is a non-element, non-document node that acts as the root of a
832/// shadow tree. Its `children` (on the owning [`Node`]) are the top-level nodes
833/// of the shadow tree. The light-DOM children of the host element are
834/// distributed into any `<slot>` elements within this tree to form the
835/// "flattened tree" that is used for style resolution, layout and painting.
836#[derive(Debug, Clone)]
837pub struct ShadowRootData {
838    /// The node id of the host element that this shadow root is attached to.
839    pub host: NodeId,
840    /// The encapsulation mode of this shadow root.
841    pub mode: ShadowRootMode,
842    /// Node ids of `<style>` elements within this shadow root, in document
843    /// order. Used to build the scoped stylesheet set for this shadow tree.
844    pub stylesheet_nodes: Vec<NodeId>,
845}
846
847impl ShadowRootData {
848    pub fn new(host: NodeId, mode: ShadowRootMode) -> Self {
849        Self {
850            host,
851            mode,
852            stylesheet_nodes: Vec::new(),
853        }
854    }
855}
856
857/// The different kinds of nodes in the DOM.
858#[derive(Debug, Clone)]
859pub enum NodeData {
860    /// The `Document` itself - the root node of a HTML document.
861    Document(Box<DocumentData>),
862
863    /// An element with attributes.
864    Element(Box<ElementData>),
865
866    /// An anonymous block box
867    AnonymousBlock(Box<ElementData>),
868
869    /// A text node.
870    Text(TextNodeData),
871
872    /// A comment.
873    Comment {
874        /// The textual content of the comment
875        contents: String,
876    },
877
878    /// A `DocumentFragment`: a parentless container whose children move when
879    /// it is inserted, and which never appears in the box tree itself.
880    DocumentFragment,
881
882    /// The root of a shadow tree attached to a host element.
883    ShadowRoot(ShadowRootData),
884    // /// A `DOCTYPE` with name, public id, and system id. See
885    // /// [document type declaration on wikipedia][https://en.wikipedia.org/wiki/Document_type_declaration]
886    // Doctype { name: String, public_id: String, system_id: String },
887
888    // /// A Processing instruction.
889    // ProcessingInstruction { target: String, contents: String },
890}
891
892impl NodeData {
893    pub fn downcast_element(&self) -> Option<&ElementData> {
894        match self {
895            Self::Element(data) => Some(data),
896            Self::AnonymousBlock(data) => Some(data),
897            _ => None,
898        }
899    }
900
901    pub fn downcast_element_mut(&mut self) -> Option<&mut ElementData> {
902        match self {
903            Self::Element(data) => Some(data),
904            Self::AnonymousBlock(data) => Some(data),
905            _ => None,
906        }
907    }
908
909    pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
910        let Some(elem) = self.downcast_element() else {
911            return false;
912        };
913        *name == elem.name.local
914    }
915
916    pub fn attrs(&self) -> Option<&[Attribute]> {
917        Some(&self.downcast_element()?.attrs)
918    }
919
920    pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
921        self.downcast_element()?.attr(name)
922    }
923
924    pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
925        self.downcast_element()
926            .is_some_and(|elem| elem.has_attr(name))
927    }
928
929    pub fn kind(&self) -> NodeKind {
930        match self {
931            NodeData::Document(_) => NodeKind::Document,
932            NodeData::Element(_) => NodeKind::Element,
933            NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
934            NodeData::Text(_) => NodeKind::Text,
935            NodeData::Comment { .. } => NodeKind::Comment,
936            NodeData::DocumentFragment => NodeKind::DocumentFragment,
937            NodeData::ShadowRoot(_) => NodeKind::ShadowRoot,
938        }
939    }
940
941    pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
942        match self {
943            Self::ShadowRoot(data) => Some(data),
944            _ => None,
945        }
946    }
947
948    pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
949        match self {
950            Self::ShadowRoot(data) => Some(data),
951            _ => None,
952        }
953    }
954}
955
956#[derive(Debug, Clone)]
957pub struct TextNodeData {
958    /// The textual content of the text node
959    pub content: String,
960}
961
962impl TextNodeData {
963    pub fn new(content: String) -> Self {
964        Self { content }
965    }
966}
967
968/*
969-> Computed styles
970-> Layout
971-----> Needs to happen only when styles are computed
972*/
973
974// type DomRefCell<T> = RefCell<T>;
975
976// pub struct DomData {
977//     // ... we can probs just get away with using the html5ever types directly. basically just using the servo dom, but without the bindings
978//     local_name: html5ever::LocalName,
979//     tag_name: html5ever::QualName,
980//     namespace: html5ever::Namespace,
981//     prefix: DomRefCell<Option<html5ever::Prefix>>,
982//     attrs: DomRefCell<Vec<Attr>>,
983//     // attrs: DomRefCell<Vec<Dom<Attr>>>,
984//     id_attribute: DomRefCell<Option<Atom>>,
985//     is: DomRefCell<Option<LocalName>>,
986//     // style_attribute: DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>>,
987//     // attr_list: MutNullableDom<NamedNodeMap>,
988//     // class_list: MutNullableDom<DOMTokenList>,
989//     state: Cell<ElementState>,
990// }
991
992impl Node {
993    pub fn tree(&self) -> &crate::NodeTree {
994        unsafe { &*self.tree }
995    }
996
997    #[track_caller]
998    pub fn with(&self, id: NodeId) -> &Node {
999        self.tree().get(id).unwrap()
1000    }
1001
1002    pub fn print_tree(&self, level: usize) {
1003        println!(
1004            "{} {} {:?} {} {:?}",
1005            "  ".repeat(level),
1006            self.id,
1007            self.parent,
1008            self.node_debug_str().replace('\n', ""),
1009            self.children
1010        );
1011        // println!("{} {:?}", "  ".repeat(level), self.children);
1012        for child_id in self.children.iter() {
1013            let child = self.with(*child_id);
1014            child.print_tree(level + 1)
1015        }
1016    }
1017
1018    // Get the index of the current node in the parents child list
1019    pub fn index_of_child(&self, child_id: NodeId) -> Option<usize> {
1020        self.children.iter().position(|id| *id == child_id)
1021    }
1022
1023    // Get the index of the current node in the parents child list
1024    pub fn child_index(&self) -> Option<usize> {
1025        self.tree()[self.parent?]
1026            .children
1027            .iter()
1028            .position(|id| *id == self.id)
1029    }
1030
1031    // Get the nth node in the parents child list
1032    pub fn forward(&self, n: usize) -> Option<&Node> {
1033        let child_idx = self.child_index().unwrap_or(0);
1034        self.tree()[self.parent?]
1035            .children
1036            .get(child_idx + n)
1037            .map(|id| self.with(*id))
1038    }
1039
1040    pub fn backward(&self, n: usize) -> Option<&Node> {
1041        let child_idx = self.child_index().unwrap_or(0);
1042        if child_idx < n {
1043            return None;
1044        }
1045
1046        self.tree()[self.parent?]
1047            .children
1048            .get(child_idx - n)
1049            .map(|id| self.with(*id))
1050    }
1051
1052    pub fn is_element(&self) -> bool {
1053        matches!(self.data, NodeData::Element { .. })
1054    }
1055
1056    pub fn is_anonymous(&self) -> bool {
1057        matches!(self.data, NodeData::AnonymousBlock { .. })
1058    }
1059
1060    pub fn is_shadow_root(&self) -> bool {
1061        matches!(self.data, NodeData::ShadowRoot { .. })
1062    }
1063
1064    pub fn shadow_root_data(&self) -> Option<&ShadowRootData> {
1065        self.data.shadow_root_data()
1066    }
1067
1068    pub fn shadow_root_data_mut(&mut self) -> Option<&mut ShadowRootData> {
1069        self.data.shadow_root_data_mut()
1070    }
1071
1072    /// If this node is a shadow host (i.e. has an attached shadow root),
1073    /// returns the node id of its shadow root.
1074    pub fn shadow_root_id(&self) -> Option<NodeId> {
1075        self.element_data().and_then(|el| el.shadow_root)
1076    }
1077
1078    /// The children to use for layout and painting. For shadow hosts and
1079    /// `<slot>` elements this is the "flattened tree" children; for all other
1080    /// nodes it is the regular DOM [`children`](Self::children).
1081    #[cfg(feature = "shadow-dom")]
1082    pub fn layout_dom_children(&self) -> &[NodeId] {
1083        match &self.flattened_children {
1084            Some(children) => children,
1085            None => &self.children,
1086        }
1087    }
1088
1089    /// The children to use for layout and painting.
1090    #[cfg(not(feature = "shadow-dom"))]
1091    #[inline(always)]
1092    pub fn layout_dom_children(&self) -> &[NodeId] {
1093        &self.children
1094    }
1095
1096    pub fn is_text_node(&self) -> bool {
1097        matches!(self.data, NodeData::Text { .. })
1098    }
1099
1100    pub fn element_data(&self) -> Option<&ElementData> {
1101        match self.data {
1102            NodeData::Element(ref data) => Some(data),
1103            NodeData::AnonymousBlock(ref data) => Some(data),
1104            _ => None,
1105        }
1106    }
1107
1108    pub fn element_data_mut(&mut self) -> Option<&mut ElementData> {
1109        match self.data {
1110            NodeData::Element(ref mut data) => Some(data),
1111            NodeData::AnonymousBlock(ref mut data) => Some(data),
1112            _ => None,
1113        }
1114    }
1115
1116    pub fn text_data(&self) -> Option<&TextNodeData> {
1117        match self.data {
1118            NodeData::Text(ref data) => Some(data),
1119            _ => None,
1120        }
1121    }
1122
1123    pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
1124        match self.data {
1125            NodeData::Text(ref mut data) => Some(data),
1126            _ => None,
1127        }
1128    }
1129
1130    pub fn node_debug_str(&self) -> String {
1131        let mut s = String::new();
1132
1133        match &self.data {
1134            NodeData::Document(_) => write!(s, "DOCUMENT"),
1135            NodeData::DocumentFragment => write!(s, "FRAGMENT"),
1136            // NodeData::Doctype { name, .. } => write!(s, "DOCTYPE {name}"),
1137            NodeData::Text(data) => {
1138                let bytes = data.content.as_bytes();
1139                write!(
1140                    s,
1141                    "TEXT {}",
1142                    std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
1143                        .unwrap_or("INVALID UTF8")
1144                )
1145            }
1146            NodeData::Comment { .. } => write!(s, "COMMENT"),
1147            NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
1148            NodeData::ShadowRoot(data) => write!(s, "#shadow-root ({:?})", data.mode),
1149            NodeData::Element(data) => {
1150                let name = &data.name;
1151                let class = self.attr(local_name!("class")).unwrap_or("");
1152                let id = self.attr(local_name!("id")).unwrap_or("");
1153                let display = self.display_constructed_as().to_css_string();
1154                write!(s, "<{}", name.local).unwrap();
1155                if !id.is_empty() {
1156                    write!(s, " #{id}").unwrap();
1157                }
1158                if !class.is_empty() {
1159                    if class.contains(' ') {
1160                        write!(s, " class=\"{class}\"").unwrap()
1161                    } else {
1162                        write!(s, " .{class}").unwrap()
1163                    }
1164                }
1165                write!(s, "> ({display})")
1166            } // NodeData::ProcessingInstruction { .. } => write!(s, "ProcessingInstruction"),
1167        }
1168        .unwrap();
1169        s
1170    }
1171
1172    /// Renders the HTML of this node and all its children as a `String` without extra whitespace.
1173    ///
1174    /// Example output:
1175    ///
1176    /// ```text
1177    /// <html><head /><body><main id="main"><div class="arbitrary-class" /></main></body></html>
1178    /// ```
1179    pub fn outer_html(&self) -> String {
1180        let mut output = String::new();
1181        self.write_outer_html(&mut output);
1182        output
1183    }
1184
1185    /// Renders the HTML of this node and all its children as a `String` with whitespace for human
1186    /// readability.
1187    ///
1188    /// Example output:
1189    ///
1190    /// ```text
1191    /// <html>
1192    ///   <head />
1193    ///   <body>
1194    ///     <main id="main">
1195    ///       <div class="arbitrary-class" />
1196    ///     </main>
1197    ///   </body>
1198    /// </html>
1199    /// ```
1200    pub fn outer_html_pretty(&self) -> String {
1201        let mut output = String::new();
1202        self.write_outer_html_pretty(&mut output);
1203        output
1204    }
1205
1206    pub fn write_outer_html(&self, writer: &mut String) {
1207        self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, None);
1208    }
1209
1210    #[cfg(feature = "svg")]
1211    pub(crate) fn write_outer_html_with_current_color(
1212        &self,
1213        writer: &mut String,
1214        current_color: &str,
1215    ) {
1216        self.write_outer_html_in_style(writer, OutputStyle::Normal, 0, Some(current_color));
1217    }
1218
1219    pub fn write_outer_html_pretty(&self, writer: &mut String) {
1220        self.write_outer_html_in_style(writer, OutputStyle::Pretty, 0, None);
1221    }
1222
1223    fn write_outer_html_in_style(
1224        &self,
1225        writer: &mut String,
1226        style: OutputStyle,
1227        nesting: usize,
1228        current_color_override: Option<&str>,
1229    ) {
1230        const INDENT: &str = "  ";
1231        let has_children = !self.children.is_empty();
1232        let computed_current_color = || {
1233            self.primary_styles()
1234                .map(|style| style.clone_color())
1235                .map(|color| crate::util::absolute_color_to_svg_css(&color))
1236        };
1237        let current_color = current_color_override
1238            .map(ToOwned::to_owned)
1239            .or_else(computed_current_color);
1240
1241        match &self.data {
1242            NodeData::Document(_) => {}
1243            NodeData::Comment { .. } => {}
1244            NodeData::AnonymousBlock(_) => {}
1245            NodeData::ShadowRoot(_) => {}
1246            // A fragment serialises as nothing of its own: the spec says its
1247            // children are what gets inserted, and it is never in a document
1248            // to be serialised from in the first place.
1249            NodeData::DocumentFragment => {}
1250            // NodeData::Doctype { name, .. } => write!(s, "DOCTYPE {name}"),
1251            NodeData::Text(data) => {
1252                if matches!(style, OutputStyle::Pretty) {
1253                    for _ in 0..nesting {
1254                        writer.push_str(INDENT);
1255                    }
1256                }
1257                writer.push_str(data.content.as_str());
1258                if matches!(style, OutputStyle::Pretty) {
1259                    writer.push('\n');
1260                }
1261            }
1262            NodeData::Element(data) => {
1263                if matches!(style, OutputStyle::Pretty) {
1264                    for _ in 0..nesting {
1265                        writer.push_str(INDENT);
1266                    }
1267                }
1268                writer.push('<');
1269                writer.push_str(&data.name.local);
1270
1271                for attr in data.attrs() {
1272                    writer.push(' ');
1273                    writer.push_str(&attr.name.local);
1274                    writer.push_str("=\"");
1275                    #[allow(clippy::unnecessary_unwrap)] // Convert to if-let chain once stabilised
1276                    if current_color.is_some() && attr.value.contains("currentColor") {
1277                        let value = attr
1278                            .value
1279                            .replace("currentColor", current_color.as_ref().unwrap());
1280                        encode_quoted_attribute_to_string(&value, writer);
1281                    } else {
1282                        encode_quoted_attribute_to_string(&attr.value, writer);
1283                    }
1284                    writer.push('"');
1285                }
1286                if !has_children {
1287                    writer.push_str(" /");
1288                }
1289                writer.push('>');
1290                if matches!(style, OutputStyle::Pretty) {
1291                    writer.push('\n');
1292                }
1293
1294                if has_children {
1295                    for &child_id in &self.children {
1296                        self.tree()[child_id].write_outer_html_in_style(
1297                            writer,
1298                            style,
1299                            nesting + 1,
1300                            current_color_override,
1301                        );
1302                    }
1303
1304                    if matches!(style, OutputStyle::Pretty) {
1305                        for _ in 0..nesting {
1306                            writer.push_str(INDENT);
1307                        }
1308                    }
1309                    writer.push_str("</");
1310                    writer.push_str(&data.name.local);
1311                    writer.push('>');
1312                    if matches!(style, OutputStyle::Pretty) {
1313                        writer.push('\n');
1314                    }
1315                }
1316            }
1317        }
1318    }
1319
1320    pub fn attrs(&self) -> Option<&[Attribute]> {
1321        Some(&self.element_data()?.attrs)
1322    }
1323
1324    pub fn attr(&self, name: LocalName) -> Option<&str> {
1325        let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
1326        Some(&attr.value)
1327    }
1328
1329    pub fn primary_styles(&self) -> Option<impl Deref<Target = ServoArc<ComputedValues>>> {
1330        self.stylo_element_data_opt()
1331            .and_then(|stylo| stylo.primary_styles())
1332    }
1333
1334    pub fn text_content(&self) -> String {
1335        let mut out = String::new();
1336        self.write_text_content(&mut out);
1337        out
1338    }
1339
1340    /// Write this subtree's text content into any [`std::fmt::Write`] sink.
1341    ///
1342    /// Public and generic so that a caller which does not want a `String` does
1343    /// not have to fork this traversal to avoid one. `blitz-dom-api`'s
1344    /// buffer-writing reader passes a sink that counts, and then one that fills
1345    /// a caller-supplied slice; a private copy of this walk in that crate would
1346    /// silently disagree with this one the first time a `NodeData` variant is
1347    /// added here.
1348    ///
1349    /// The sinks callers pass do not fail, and `String`'s never has, so nothing
1350    /// in this crate inspects the `Result`. It is kept in the signature because
1351    /// it is `fmt::Write`'s, not because there is an error to handle.
1352    pub fn write_text_content<W: Write>(&self, out: &mut W) {
1353        match &self.data {
1354            NodeData::Text(data) => {
1355                let _ = out.write_str(&data.content);
1356            }
1357            NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
1358                for child_id in self.children.iter() {
1359                    self.with(*child_id).write_text_content(out);
1360                }
1361            }
1362            _ => {}
1363        }
1364    }
1365
1366    pub fn flush_style_attribute(&mut self, url_extra_data: &UrlExtraData) {
1367        if let NodeData::Element(ref mut elem_data) = self.data {
1368            if let Some(guard) = elem_data.guard.clone() {
1369                elem_data.flush_style_attribute(&guard, url_extra_data);
1370            }
1371        }
1372    }
1373
1374    pub fn order(&self) -> i32 {
1375        self.primary_styles()
1376            .map(|s| match s.pseudo() {
1377                Some(PseudoElement::Before) => i32::MIN,
1378                Some(PseudoElement::After) => i32::MAX,
1379                _ => s.clone_order(),
1380            })
1381            .unwrap_or(0)
1382    }
1383
1384    pub fn z_index(&self) -> i32 {
1385        self.primary_styles()
1386            .map(|s| s.clone_z_index().integer_or(0))
1387            .unwrap_or(0)
1388    }
1389
1390    // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Stacking_context#features_creating_stacking_contexts
1391    pub fn is_stacking_context_root(&self, is_flex_or_grid_item: bool) -> bool {
1392        let Some(style) = self.primary_styles() else {
1393            return false;
1394        };
1395
1396        let position = style.clone_position();
1397        let has_z_index = !style.clone_z_index().is_auto();
1398
1399        if style.clone_opacity() != 1.0 {
1400            return true;
1401        }
1402
1403        let position_based = match position {
1404            Position::Fixed | Position::Sticky => true,
1405            Position::Relative | Position::Absolute => has_z_index,
1406            Position::Static => has_z_index && is_flex_or_grid_item,
1407        };
1408        if position_based {
1409            return true;
1410        }
1411
1412        // This runs while the stacking-context tree is built, before
1413        // `resolve_transforms` populates the cached device-space matrix. Using
1414        // that cache here makes the first frame treat a transformed node as an
1415        // ordinary box, hoist its painted children into the parent, and only
1416        // repair the tree after the first hover/restyle. The computed CSS value
1417        // is already available and is the source of truth for whether the node
1418        // establishes a stacking context.
1419        let box_styles = style.get_box();
1420        if !box_styles.transform.0.is_empty()
1421            || !matches!(box_styles.translate, Translate::None)
1422            || !matches!(box_styles.rotate, Rotate::None)
1423            || !matches!(box_styles.scale, Scale::None)
1424        {
1425            return true;
1426        }
1427
1428        // `isolation: isolate` exists precisely to create a stacking context
1429        // without any other visual effect. Ignoring it lets a negative z-index
1430        // descendant escape to an ancestor context, where it is painted before
1431        // (and so underneath) the backgrounds of the boxes in between.
1432        if box_styles.isolation == Isolation::Isolate {
1433            return true;
1434        }
1435
1436        // TODO: mix-blend-mode
1437        // TODO: filter
1438        // TODO: clip-path
1439        // TODO: mask
1440        // TODO: contain
1441
1442        false
1443    }
1444
1445    /// Takes an (x, y) position (relative to the *parent's* top-left corner) and returns:
1446    ///    - None if the position is outside of this node's bounds
1447    ///    - Some(HitResult) if the position is within the node but doesn't match any children
1448    ///    - The result of recursively calling child.hit() on the the child element that is
1449    ///      positioned at that position if there is one.
1450    ///
1451    /// TODO: z-index
1452    /// (If multiple children are positioned at the position then a random one will be recursed into)
1453    pub fn hit(&self, x: f32, y: f32, scale: f64) -> Option<HitResult> {
1454        self.hit_inner(x, y, scale, &mut None)
1455    }
1456
1457    /// [`hit`](Self::hit), also resolving the innermost overlay scrollbar
1458    /// thumb under the point into `scrollbar` during the same descent (so
1459    /// thumb hit-testing shares the exact coordinate handling — transforms
1460    /// included — of every other hit test).
1461    pub(crate) fn hit_inner(
1462        &self,
1463        x: f32,
1464        y: f32,
1465        scale: f64,
1466        scrollbar: &mut Option<crate::node::ScrollbarRef>,
1467    ) -> Option<HitResult> {
1468        use style::computed_values::pointer_events::T as PointerEvents;
1469        use style::computed_values::visibility::T as Visibility;
1470
1471        // A hidden subtree takes no hits.
1472        //
1473        // This never needed saying while hiding a pane destroyed its boxes: a
1474        // hidden subtree had nothing to test against. It keeps its boxes now,
1475        // and their `final_layout` is whatever it was when the pane was last
1476        // visible — full size, in place, over the tab in front. A retained tab
1477        // measured 331 of its 370 elements still carrying live geometry after
1478        // being hidden, and every one of them was a click target.
1479        if matches!(self.style().display, taffy::Display::None) {
1480            return None;
1481        }
1482
1483        // Don't hit on visbility:hidden elements
1484        if let Some(style) = self.primary_styles() {
1485            if matches!(
1486                style.clone_visibility(),
1487                Visibility::Hidden | Visibility::Collapse
1488            ) {
1489                return None;
1490            }
1491        }
1492
1493        // pointer-events:none makes this element transparent to hits, but its
1494        // descendants are still tested (one may restore pointer-events:auto).
1495        let pointer_events_none = self
1496            .primary_styles()
1497            .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1498
1499        let mut x = x - self.final_layout().location.x + self.scroll_offset().x as f32;
1500        let mut y = y - self.final_layout().location.y + self.scroll_offset().y as f32;
1501
1502        if let Some(t) = *self.transform() {
1503            let p = t.inverse() * kurbo::Point::new(x as f64 * scale, y as f64 * scale);
1504            x = (p.x / scale) as f32;
1505            y = (p.y / scale) as f32;
1506        }
1507
1508        let size = self.final_layout().size;
1509        let matches_self = !(x < 0.0
1510            || x > size.width + self.scroll_offset().x as f32
1511            || y < 0.0
1512            || y > size.height + self.scroll_offset().y as f32);
1513
1514        let content_size = self.final_layout().content_size;
1515        let matches_content = !(x < 0.0
1516            || x > content_size.width + self.scroll_offset().x as f32
1517            || y < 0.0
1518            || y > content_size.height + self.scroll_offset().y as f32);
1519
1520        let matches_hoisted_content = match &self.stacking_context {
1521            Some(sc) => {
1522                let content_area = sc.content_area;
1523                x >= content_area.left + self.scroll_offset().x as f32
1524                    && x <= content_area.right + self.scroll_offset().x as f32
1525                    && y >= content_area.top + self.scroll_offset().y as f32
1526                    && y <= content_area.bottom + self.scroll_offset().y as f32
1527            }
1528            None => false,
1529        };
1530
1531        // `scrollable_overflow` is stored in device (scaled) pixels, whereas the
1532        // coordinates here are in CSS pixels, so unscale it before comparing.
1533        let overflow = *self.scrollable_overflow();
1534
1535        let matches_overflow = x >= (overflow.x0 / scale) as f32
1536            && x <= (overflow.x1 / scale) as f32
1537            && y >= (overflow.y0 / scale) as f32
1538            && y <= (overflow.y1 / scale) as f32;
1539
1540        if !matches_self && !matches_content && !matches_hoisted_content && !matches_overflow {
1541            return None;
1542        }
1543
1544        // Descendants overwrite, so the innermost scroll container's thumb
1545        // wins. Thumb coords are border-box relative (unscrolled).
1546        if matches_self
1547            && let Some(sb) = self.scrollbar_at_local(
1548                (x - self.scroll_offset().x as f32) as f64,
1549                (y - self.scroll_offset().y as f32) as f64,
1550            )
1551        {
1552            *scrollbar = Some(sb);
1553        }
1554
1555        if self.flags.is_inline_root() {
1556            let content_box_offset = taffy::Point {
1557                x: self.final_layout().padding.left + self.final_layout().border.left,
1558                y: self.final_layout().padding.top + self.final_layout().border.top,
1559            };
1560            x -= content_box_offset.x;
1561            y -= content_box_offset.y;
1562        }
1563
1564        // Positive z_index hoisted children
1565        if matches_hoisted_content {
1566            if let Some(hoisted) = &self.stacking_context {
1567                for hoisted_child in hoisted.pos_z_hoisted_children().rev() {
1568                    let x = x - hoisted_child.position.x;
1569                    let y = y - hoisted_child.position.y;
1570                    if let Some(hit) = self
1571                        .with(hoisted_child.node_id)
1572                        .hit_inner(x, y, scale, scrollbar)
1573                    {
1574                        return Some(hit);
1575                    }
1576                }
1577            }
1578        }
1579
1580        // Call `.hit()` on each child in turn. If any return `Some` then return that value. Else return `Some(self.id).
1581        for child_id in self.paint_children.borrow().iter().flatten().rev() {
1582            if let Some(hit) = self.with(*child_id).hit_inner(x, y, scale, scrollbar) {
1583                return Some(hit);
1584            }
1585        }
1586
1587        // Negative z_index hoisted children
1588        if matches_hoisted_content {
1589            if let Some(hoisted) = &self.stacking_context {
1590                for hoisted_child in hoisted.neg_z_hoisted_children().rev() {
1591                    let x = x - hoisted_child.position.x;
1592                    let y = y - hoisted_child.position.y;
1593                    if let Some(hit) = self
1594                        .with(hoisted_child.node_id)
1595                        .hit_inner(x, y, scale, scrollbar)
1596                    {
1597                        return Some(hit);
1598                    }
1599                }
1600            }
1601        }
1602
1603        // Inline children
1604        if self.flags.is_inline_root() {
1605            let element_data = &self.element_data().unwrap();
1606            if let Some(ild) = element_data.inline_layout_data.as_ref() {
1607                let layout = &ild.layout;
1608                let scale = layout.scale();
1609
1610                if let Some((cluster, _side)) =
1611                    Cluster::from_point_exact(layout, x * scale, y * scale)
1612                {
1613                    let style_index = cluster.glyphs().next()?.style_index();
1614                    let node_id = layout.styles()[style_index].brush.id;
1615                    let text_pointer_events_none = self
1616                        .with(node_id)
1617                        .primary_styles()
1618                        .is_some_and(|style| style.clone_pointer_events() == PointerEvents::None);
1619                    if !text_pointer_events_none {
1620                        return Some(HitResult {
1621                            node_id,
1622                            x,
1623                            y,
1624                            is_text: true,
1625                        });
1626                    }
1627                }
1628            }
1629        }
1630
1631        // Self (this node)
1632        if matches_self && !pointer_events_none {
1633            return Some(HitResult {
1634                node_id: self.id,
1635                x,
1636                y,
1637                is_text: false,
1638            });
1639        }
1640
1641        None
1642    }
1643
1644    /// Find the inline root ancestor of this node (or self if this is an inline root).
1645    /// Returns None if no inline root ancestor exists.
1646    pub fn inline_root_ancestor(&self) -> Option<&Node> {
1647        let mut node = self;
1648        loop {
1649            if node.flags.is_inline_root() {
1650                return Some(node);
1651            }
1652            let id = node.layout_parent.get()?;
1653            node = self.with(id);
1654        }
1655    }
1656
1657    /// Get the text byte offset at a given point, using coordinates already transformed
1658    /// to be relative to this inline root's content box.
1659    /// Returns Some(byte_offset) if the point hits text, None otherwise.
1660    pub fn text_offset_at_point(&self, x: f32, y: f32) -> Option<usize> {
1661        if !self.flags.is_inline_root() {
1662            return None;
1663        }
1664
1665        let element_data = self.element_data()?;
1666        let inline_layout = element_data.inline_layout_data.as_ref()?;
1667        let layout = &inline_layout.layout;
1668        let scale = layout.scale();
1669
1670        // Use Parley's cluster hit testing (from_point is more forgiving than from_point_exact)
1671        let (cluster, side) = Cluster::from_point(layout, x * scale, y * scale)?;
1672
1673        // Determine byte offset based on which side of the cluster was clicked
1674        // For LTR text: left side = start of cluster, right side = end of cluster
1675        // For RTL text: left side = end of cluster, right side = start of cluster
1676        // Also, explicit line breaks should always use start to avoid cursor appearing on next line
1677        let is_leading = side == ClusterSide::Left;
1678        let offset = if cluster.is_rtl() {
1679            if is_leading {
1680                cluster.text_range().end
1681            } else {
1682                cluster.text_range().start
1683            }
1684        } else {
1685            // LTR text
1686            if is_leading || cluster.is_line_break() == Some(BreakReason::Explicit) {
1687                cluster.text_range().start
1688            } else {
1689                cluster.text_range().end
1690            }
1691        };
1692
1693        Some(offset)
1694    }
1695
1696    /// The byte range of the word or line at a point, for a multi-click
1697    /// selection.
1698    ///
1699    /// See [`TextGranularity`] for which click count maps to which unit. Coordinates are relative to this inline root's content box,
1700    /// as for [`text_offset_at_point`](Self::text_offset_at_point).
1701    ///
1702    /// Parley owns the boundary rules (it is what an `<input>` already selects
1703    /// with), so this asks it rather than scanning for spaces: word breaks are
1704    /// a Unicode segmentation question, not a whitespace one.
1705    pub fn text_range_at_point(
1706        &self,
1707        x: f32,
1708        y: f32,
1709        granularity: TextGranularity,
1710    ) -> Option<Range<usize>> {
1711        if !self.flags.is_inline_root() {
1712            return None;
1713        }
1714
1715        let element_data = self.element_data()?;
1716        let inline_layout = element_data.inline_layout_data.as_ref()?;
1717        let layout = &inline_layout.layout;
1718        let scale = layout.scale();
1719        let (x, y) = (x * scale, y * scale);
1720
1721        // Bail when the point misses the text entirely: `Selection` answers an
1722        // out-of-range point with a collapsed cursor at the end of the text,
1723        // which would read as "selected nothing at the very end" rather than
1724        // as a miss.
1725        Cluster::from_point(layout, x, y)?;
1726
1727        let selection = match granularity {
1728            TextGranularity::Word => Selection::word_from_point(layout, x, y),
1729            // The hard line, so a soft-wrapped paragraph selects as the whole
1730            // paragraph. That is what a triple click does elsewhere, and it is
1731            // what `select_hard_line_at_point` gives a text input.
1732            TextGranularity::Line => Selection::hard_line_from_point(layout, x, y),
1733        };
1734
1735        let range = selection.text_range();
1736        if range.is_empty() { None } else { Some(range) }
1737    }
1738
1739    /// Computes the Document-relative coordinates of the `Node`
1740    pub fn absolute_position(&self, x: f32, y: f32) -> crate::util::Point<f32> {
1741        // A scroll offset moves this node's descendants, not its own border
1742        // box. Parent recursion applies each ancestor offset to the child.
1743        let x = x + self.final_layout().location.x;
1744        let y = y + self.final_layout().location.y;
1745
1746        // Recurse up the layout hierarchy
1747        self.layout_parent
1748            .get()
1749            .and_then(|id| self.tree().get(id))
1750            .map(|parent| {
1751                parent.absolute_position(
1752                    x - parent.scroll_offset().x as f32,
1753                    y - parent.scroll_offset().y as f32,
1754                )
1755            })
1756            .unwrap_or(crate::util::Point { x, y })
1757    }
1758
1759    /// Whether this node can act as an [`offset_parent`](Self::offset_parent): a positioned
1760    /// element, or one of the elements that always qualify (`body`, `td`, `th`).
1761    fn is_offset_parent(&self) -> bool {
1762        let Some(styles) = self.primary_styles() else {
1763            return false;
1764        };
1765        if styles.get_box().position != Position::Static {
1766            return true;
1767        }
1768        self.data.is_element_with_tag_name(&local_name!("body"))
1769            || self.data.is_element_with_tag_name(&local_name!("td"))
1770            || self.data.is_element_with_tag_name(&local_name!("th"))
1771    }
1772
1773    /// The nearest layout ancestor that is an offset parent, as in CSSOM View's
1774    /// `offsetParent`.
1775    ///
1776    /// The predicate is `is_offset_parent`, which is private, so this is not a
1777    /// link: rustdoc rejects a public item linking to a private one under
1778    /// `-D warnings`, and the alternative is publishing a helper nothing else
1779    /// needs.
1780    pub fn offset_parent(&self) -> Option<&Node> {
1781        let mut node = self;
1782        loop {
1783            node = self.with(node.layout_parent.get()?);
1784            if node.is_offset_parent() {
1785                return Some(node);
1786            }
1787        }
1788    }
1789
1790    /// CSSOM View's `offsetLeft`/`offsetTop`: the offset of this node's border box from the
1791    /// padding edge of its [`offset_parent`](Self::offset_parent).
1792    pub fn offset_top_left(&self) -> crate::util::Point<f32> {
1793        let mut x = 0.0;
1794        let mut y = 0.0;
1795        let mut current = self;
1796        loop {
1797            let layout = current.final_layout();
1798            x += layout.location.x;
1799            y += layout.location.y;
1800
1801            let Some(parent_id) = current.layout_parent.get() else {
1802                break;
1803            };
1804            let parent = self.with(parent_id);
1805            if parent.is_offset_parent() {
1806                let border = parent.final_layout().border;
1807                x -= border.left;
1808                y -= border.top;
1809                break;
1810            }
1811            current = parent;
1812        }
1813        crate::util::Point { x, y }
1814    }
1815
1816    /// Creates a synthetic click event
1817    pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1818        DomEventData::Click(self.synthetic_click_event_data(mods))
1819    }
1820
1821    pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzPointerEvent {
1822        let absolute_position = self.absolute_position(0.0, 0.0);
1823        let x = absolute_position.x + (self.final_layout().size.width / 2.0);
1824        let y = absolute_position.y + (self.final_layout().size.height / 2.0);
1825
1826        BlitzPointerEvent {
1827            id: BlitzPointerId::Mouse,
1828            is_primary: true,
1829            coords: PointerCoords {
1830                page_x: x,
1831                page_y: y,
1832
1833                // TODO: should these be different?
1834                screen_x: x,
1835                screen_y: y,
1836                client_x: x,
1837                client_y: y,
1838            },
1839            mods,
1840            button: Default::default(),
1841            buttons: Default::default(),
1842            details: Default::default(),
1843            element: Default::default(),
1844            active_pointers: Default::default(),
1845        }
1846    }
1847}
1848
1849/// It might be wrong to expose this since what does *equality* mean outside the dom?
1850impl PartialEq for Node {
1851    fn eq(&self, other: &Self) -> bool {
1852        self.id == other.id
1853    }
1854}
1855
1856impl Eq for Node {}
1857
1858impl std::fmt::Debug for Node {
1859    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1860        // FIXME: update to reflect changes to fields
1861        f.debug_struct("NodeData")
1862            .field("parent", &self.parent)
1863            .field("id", &self.id)
1864            .field("is_inline_root", &self.flags.is_inline_root())
1865            .field("children", &self.children)
1866            .field("layout_children", &self.layout_children.borrow())
1867            // .field("style", &self.style)
1868            .field("node", &self.data)
1869            .field("stylo_element_data", &self.stylo_element_data_opt())
1870            // .field("unrounded_layout", &self.unrounded_layout)
1871            // .field("final_layout", &self.final_layout)
1872            .finish()
1873    }
1874}
1875
1876#[cfg(test)]
1877mod test {
1878    use style_dom::ElementState;
1879
1880    use crate::{Attribute, BaseDocument, DocumentConfig, ElementData, NodeData, qual_name};
1881
1882    #[test]
1883    fn absolute_position_tolerates_a_detached_layout_parent() {
1884        let mut document = BaseDocument::new(DocumentConfig::default());
1885        let parent = document.create_node(NodeData::Element(Box::new(ElementData::new(
1886            qual_name!("section"),
1887            vec![],
1888        ))));
1889        let child = document.create_node(NodeData::Element(Box::new(ElementData::new(
1890            qual_name!("button"),
1891            vec![],
1892        ))));
1893        document
1894            .get_node(child)
1895            .unwrap()
1896            .layout_parent
1897            .set(Some(parent));
1898
1899        // An event handler may detach its own ancestor before the renderer's
1900        // default-action tail finishes computing event-relative coordinates.
1901        // The child object can still be alive through that dispatch even though
1902        // its recorded layout parent has already left the slot map.
1903        document.remove_node_from_tree(parent);
1904
1905        assert_eq!(
1906            document
1907                .get_node(child)
1908                .unwrap()
1909                .absolute_position(4.0, 7.0),
1910            crate::util::Point { x: 4.0, y: 7.0 },
1911        );
1912    }
1913
1914    #[test]
1915    fn create_node_with_disabled_attr() {
1916        let mut document = BaseDocument::new(DocumentConfig::default());
1917        let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1918            qual_name!("button"),
1919            vec![Attribute {
1920                name: qual_name!("disabled"),
1921                value: "".into(),
1922            }],
1923        ))));
1924        let node = document.get_node(node).unwrap();
1925
1926        assert!(
1927            node.element_state().contains(ElementState::DISABLED),
1928            "form node is disabled"
1929        );
1930        assert!(
1931            !node.element_state().contains(ElementState::ENABLED),
1932            "form node is not enabled"
1933        );
1934    }
1935
1936    #[test]
1937    fn ignore_disabled_attr_content() {
1938        let mut document = BaseDocument::new(DocumentConfig::default());
1939        let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1940            qual_name!("button"),
1941            vec![Attribute {
1942                name: qual_name!("disabled"),
1943                value: "false".into(),
1944            }],
1945        ))));
1946        let node = document.get_node(node).unwrap();
1947
1948        assert!(
1949            node.element_state().contains(ElementState::DISABLED),
1950            "form node is disabled"
1951        );
1952        assert!(
1953            !node.element_state().contains(ElementState::ENABLED),
1954            "form node is not enabled"
1955        );
1956    }
1957
1958    #[test]
1959    fn create_node_with_ignored_disable() {
1960        let mut document = BaseDocument::new(DocumentConfig::default());
1961        let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1962            qual_name!("a"),
1963            vec![Attribute {
1964                name: qual_name!("disabled"),
1965                value: "".into(),
1966            }],
1967        ))));
1968        let node = document.get_node(node).unwrap();
1969
1970        assert!(
1971            !node.element_state().contains(ElementState::DISABLED),
1972            "Non form node cannot be disabled"
1973        );
1974        assert!(
1975            !node.element_state().contains(ElementState::ENABLED),
1976            "Non form node cannot be enabled"
1977        );
1978    }
1979
1980    #[test]
1981    fn create_empty_enabled_node() {
1982        let mut document = BaseDocument::new(DocumentConfig::default());
1983        let node = document.create_node(NodeData::Element(Box::new(ElementData::new(
1984            qual_name!("button"),
1985            vec![],
1986        ))));
1987        let node = document.get_node(node).unwrap();
1988
1989        assert!(
1990            node.element_state().contains(ElementState::ENABLED),
1991            "Button should be enabled by default"
1992        );
1993    }
1994}