blitz_dom/
node.rs

1use atomic_refcell::{AtomicRef, AtomicRefCell};
2use color::{AlphaColor, Srgb};
3use keyboard_types::Modifiers;
4use markup5ever::{LocalName, QualName, local_name};
5use parley::{Cluster, FontContext, LayoutContext};
6use peniko::kurbo;
7use selectors::matching::{ElementSelectorFlags, QuirksMode};
8use slab::Slab;
9use std::cell::{Cell, RefCell};
10use std::fmt::Write;
11use std::str::FromStr;
12use std::sync::Arc;
13use std::sync::atomic::AtomicBool;
14use style::Atom;
15use style::invalidation::element::restyle_hints::RestyleHint;
16use style::properties::ComputedValues;
17use style::properties::generated::longhands::position::computed_value::T as Position;
18use style::selector_parser::PseudoElement;
19use style::stylesheets::UrlExtraData;
20use style::values::computed::Display;
21use style::values::specified::box_::{DisplayInside, DisplayOutside};
22use style::{
23    data::ElementData,
24    properties::{PropertyDeclarationBlock, parse_style_attribute},
25    servo_arc::Arc as ServoArc,
26    shared_lock::{Locked, SharedRwLock},
27    stylesheets::CssRuleType,
28};
29use style_dom::ElementState;
30use style_traits::values::ToCss;
31use taffy::{
32    Cache,
33    prelude::{Layout, Style},
34};
35use url::Url;
36
37use crate::layout::table::TableContext;
38use blitz_traits::{BlitzMouseButtonEvent, DomEventData, HitResult};
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum DisplayOuter {
42    Block,
43    Inline,
44    None,
45}
46
47// todo: might be faster to migrate this to ecs and split apart at a different boundary
48pub struct Node {
49    // The actual tree we belong to. This is unsafe!!
50    tree: *mut Slab<Node>,
51
52    /// Our Id
53    pub id: usize,
54    /// Our parent's ID
55    pub parent: Option<usize>,
56    // What are our children?
57    pub children: Vec<usize>,
58    /// Our parent in the layout hierachy: a separate list that includes anonymous collections of inline elements
59    pub layout_parent: Cell<Option<usize>>,
60    /// A separate child list that includes anonymous collections of inline elements
61    pub layout_children: RefCell<Option<Vec<usize>>>,
62    /// The same as layout_children, but sorted by z-index
63    pub paint_children: RefCell<Option<Vec<usize>>>,
64
65    /// Node type (Element, TextNode, etc) specific data
66    pub data: NodeData,
67
68    // This little bundle of joy is our style data from stylo and a lock guard that allows access to it
69    // TODO: See if guard can be hoisted to a higher level
70    pub stylo_element_data: AtomicRefCell<Option<ElementData>>,
71    pub selector_flags: AtomicRefCell<ElementSelectorFlags>,
72    pub guard: SharedRwLock,
73    pub element_state: ElementState,
74
75    // Pseudo element nodes
76    pub before: Option<usize>,
77    pub after: Option<usize>,
78
79    // Taffy layout data:
80    pub style: Style,
81    pub has_snapshot: bool,
82    pub snapshot_handled: AtomicBool,
83    pub display_outer: DisplayOuter,
84    pub cache: Cache,
85    pub unrounded_layout: Layout,
86    pub final_layout: Layout,
87    pub scroll_offset: kurbo::Point,
88
89    // Flags
90    pub is_inline_root: bool,
91    pub is_table_root: bool,
92}
93
94impl Node {
95    pub(crate) fn new(
96        tree: *mut Slab<Node>,
97        id: usize,
98        guard: SharedRwLock,
99        data: NodeData,
100    ) -> Self {
101        Self {
102            tree,
103
104            id,
105            parent: None,
106            children: vec![],
107            layout_parent: Cell::new(None),
108            layout_children: RefCell::new(None),
109            paint_children: RefCell::new(None),
110
111            data,
112            stylo_element_data: Default::default(),
113            selector_flags: AtomicRefCell::new(ElementSelectorFlags::empty()),
114            guard,
115            element_state: ElementState::empty(),
116
117            before: None,
118            after: None,
119
120            style: Default::default(),
121            has_snapshot: false,
122            snapshot_handled: AtomicBool::new(false),
123            display_outer: DisplayOuter::Block,
124            cache: Cache::new(),
125            unrounded_layout: Layout::new(),
126            final_layout: Layout::new(),
127            scroll_offset: kurbo::Point::ZERO,
128            is_inline_root: false,
129            is_table_root: false,
130        }
131    }
132
133    pub fn pe_by_index(&self, index: usize) -> Option<usize> {
134        match index {
135            0 => self.after,
136            1 => self.before,
137            _ => panic!("Invalid pseudo element index"),
138        }
139    }
140
141    pub fn set_pe_by_index(&mut self, index: usize, value: Option<usize>) {
142        match index {
143            0 => self.after = value,
144            1 => self.before = value,
145            _ => panic!("Invalid pseudo element index"),
146        }
147    }
148
149    pub(crate) fn display_style(&self) -> Option<Display> {
150        Some(self.primary_styles().as_ref()?.clone_display())
151    }
152
153    pub fn is_or_contains_block(&self) -> bool {
154        let style = self.primary_styles();
155        let style = style.as_ref();
156
157        // Ignore out-of-flow items
158        let position = style
159            .map(|s| s.clone_position())
160            .unwrap_or(Position::Relative);
161        let is_in_flow = matches!(
162            position,
163            Position::Static | Position::Relative | Position::Sticky
164        );
165        if !is_in_flow {
166            return false;
167        }
168        let display = style
169            .map(|s| s.clone_display())
170            .unwrap_or(Display::inline());
171        match display.outside() {
172            DisplayOutside::None => false,
173            DisplayOutside::Block => true,
174            _ => {
175                if display.inside() == DisplayInside::Flow {
176                    self.children
177                        .iter()
178                        .copied()
179                        .any(|child_id| self.tree()[child_id].is_or_contains_block())
180                } else {
181                    false
182                }
183            }
184        }
185    }
186
187    pub fn is_focussable(&self) -> bool {
188        self.data
189            .downcast_element()
190            .map(|el| el.is_focussable)
191            .unwrap_or(false)
192    }
193
194    pub fn set_restyle_hint(&mut self, hint: RestyleHint) {
195        if let Some(element_data) = self.stylo_element_data.borrow_mut().as_mut() {
196            element_data.hint.insert(hint);
197        }
198    }
199
200    pub fn hover(&mut self) {
201        self.element_state.insert(ElementState::HOVER);
202        self.set_restyle_hint(RestyleHint::restyle_subtree());
203    }
204
205    pub fn unhover(&mut self) {
206        self.element_state.remove(ElementState::HOVER);
207        self.set_restyle_hint(RestyleHint::restyle_subtree());
208    }
209
210    pub fn is_hovered(&self) -> bool {
211        self.element_state.contains(ElementState::HOVER)
212    }
213
214    pub fn focus(&mut self) {
215        self.element_state
216            .insert(ElementState::FOCUS | ElementState::FOCUSRING);
217        self.set_restyle_hint(RestyleHint::restyle_subtree());
218    }
219
220    pub fn blur(&mut self) {
221        self.element_state
222            .remove(ElementState::FOCUS | ElementState::FOCUSRING);
223        self.set_restyle_hint(RestyleHint::restyle_subtree());
224    }
225
226    pub fn is_focussed(&self) -> bool {
227        self.element_state.contains(ElementState::FOCUS)
228    }
229
230    pub fn active(&mut self) {
231        self.element_state.insert(ElementState::ACTIVE);
232        self.set_restyle_hint(RestyleHint::restyle_subtree());
233    }
234
235    pub fn unactive(&mut self) {
236        self.element_state.remove(ElementState::ACTIVE);
237        self.set_restyle_hint(RestyleHint::restyle_subtree());
238    }
239
240    pub fn is_active(&self) -> bool {
241        self.element_state.contains(ElementState::ACTIVE)
242    }
243}
244
245#[derive(Debug, Clone, Copy, PartialEq)]
246pub enum NodeKind {
247    Document,
248    Element,
249    AnonymousBlock,
250    Text,
251    Comment,
252}
253
254/// The different kinds of nodes in the DOM.
255#[derive(Debug, Clone)]
256pub enum NodeData {
257    /// The `Document` itself - the root node of a HTML document.
258    Document,
259
260    /// An element with attributes.
261    Element(ElementNodeData),
262
263    /// An anonymous block box
264    AnonymousBlock(ElementNodeData),
265
266    /// A text node.
267    Text(TextNodeData),
268
269    /// A comment.
270    Comment,
271    // Comment { contents: String },
272
273    // /// A `DOCTYPE` with name, public id, and system id. See
274    // /// [document type declaration on wikipedia][https://en.wikipedia.org/wiki/Document_type_declaration]
275    // Doctype { name: String, public_id: String, system_id: String },
276
277    // /// A Processing instruction.
278    // ProcessingInstruction { target: String, contents: String },
279}
280
281impl NodeData {
282    pub fn downcast_element(&self) -> Option<&ElementNodeData> {
283        match self {
284            Self::Element(data) => Some(data),
285            Self::AnonymousBlock(data) => Some(data),
286            _ => None,
287        }
288    }
289
290    pub fn downcast_element_mut(&mut self) -> Option<&mut ElementNodeData> {
291        match self {
292            Self::Element(data) => Some(data),
293            Self::AnonymousBlock(data) => Some(data),
294            _ => None,
295        }
296    }
297
298    pub fn is_element_with_tag_name(&self, name: &impl PartialEq<LocalName>) -> bool {
299        let Some(elem) = self.downcast_element() else {
300            return false;
301        };
302        *name == elem.name.local
303    }
304
305    pub fn attrs(&self) -> Option<&[Attribute]> {
306        Some(&self.downcast_element()?.attrs)
307    }
308
309    pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
310        self.downcast_element()?.attr(name)
311    }
312
313    pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
314        self.downcast_element()
315            .is_some_and(|elem| elem.has_attr(name))
316    }
317
318    pub fn kind(&self) -> NodeKind {
319        match self {
320            NodeData::Document => NodeKind::Document,
321            NodeData::Element(_) => NodeKind::Element,
322            NodeData::AnonymousBlock(_) => NodeKind::AnonymousBlock,
323            NodeData::Text(_) => NodeKind::Text,
324            NodeData::Comment => NodeKind::Comment,
325        }
326    }
327}
328
329/// A tag attribute, e.g. `class="test"` in `<div class="test" ...>`.
330///
331/// The namespace on the attribute name is almost always ns!("").
332/// The tokenizer creates all attributes this way, but the tree
333/// builder will adjust certain attribute names inside foreign
334/// content (MathML, SVG).
335#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
336pub struct Attribute {
337    /// The name of the attribute (e.g. the `class` in `<div class="test">`)
338    pub name: QualName,
339    /// The value of the attribute (e.g. the `"test"` in `<div class="test">`)
340    pub value: String,
341}
342
343#[derive(Debug, Clone)]
344pub struct ElementNodeData {
345    /// The elements tag name, namespace and prefix
346    pub name: QualName,
347
348    /// The elements id attribute parsed as an atom (if it has one)
349    pub id: Option<Atom>,
350
351    /// The element's attributes
352    pub attrs: Vec<Attribute>,
353
354    /// Whether the element is focussable
355    pub is_focussable: bool,
356
357    /// The element's parsed style attribute (used by stylo)
358    pub style_attribute: Option<ServoArc<Locked<PropertyDeclarationBlock>>>,
359
360    /// Heterogeneous data that depends on the element's type.
361    /// For example:
362    ///   - The image data for \<img\> elements.
363    ///   - The parley Layout for inline roots.
364    ///   - The text editor for input/textarea elements
365    pub node_specific_data: NodeSpecificData,
366
367    pub background_images: Vec<Option<BackgroundImageData>>,
368
369    /// Parley text layout (elements with inline inner display mode only)
370    pub inline_layout_data: Option<Box<TextLayout>>,
371
372    //Data associated with display: list-item. Note that this display mode
373    // does not exclude inline_layout_data
374    pub list_item_data: Option<Box<ListItemLayout>>,
375
376    /// The element's template contents (\<template\> elements only)
377    pub template_contents: Option<usize>,
378    // /// Whether the node is a [HTML integration point] (https://html.spec.whatwg.org/multipage/#html-integration-point)
379    // pub mathml_annotation_xml_integration_point: bool,
380}
381
382impl ElementNodeData {
383    pub fn new(name: QualName, attrs: Vec<Attribute>) -> Self {
384        let id_attr_atom = attrs
385            .iter()
386            .find(|attr| &attr.name.local == "id")
387            .map(|attr| attr.value.as_ref())
388            .map(|value: &str| Atom::from(value));
389
390        let mut data = ElementNodeData {
391            name,
392            id: id_attr_atom,
393            attrs,
394            is_focussable: false,
395            style_attribute: Default::default(),
396            inline_layout_data: None,
397            list_item_data: None,
398            node_specific_data: NodeSpecificData::None,
399            template_contents: None,
400            background_images: Vec::new(),
401        };
402        data.flush_is_focussable();
403        data
404    }
405
406    pub fn attrs(&self) -> &[Attribute] {
407        &self.attrs
408    }
409
410    pub fn attr(&self, name: impl PartialEq<LocalName>) -> Option<&str> {
411        let attr = self.attrs.iter().find(|attr| name == attr.name.local)?;
412        Some(&attr.value)
413    }
414
415    pub fn attr_parsed<T: FromStr>(&self, name: impl PartialEq<LocalName>) -> Option<T> {
416        let attr = self.attrs.iter().find(|attr| name == attr.name.local)?;
417        attr.value.parse::<T>().ok()
418    }
419
420    /// Detects the presence of the attribute, treating *any* value as truthy.
421    pub fn has_attr(&self, name: impl PartialEq<LocalName>) -> bool {
422        self.attrs.iter().any(|attr| name == attr.name.local)
423    }
424
425    pub fn image_data(&self) -> Option<&ImageData> {
426        match &self.node_specific_data {
427            NodeSpecificData::Image(data) => Some(&**data),
428            _ => None,
429        }
430    }
431
432    pub fn image_data_mut(&mut self) -> Option<&mut ImageData> {
433        match self.node_specific_data {
434            NodeSpecificData::Image(ref mut data) => Some(&mut **data),
435            _ => None,
436        }
437    }
438
439    pub fn raster_image_data(&self) -> Option<&RasterImageData> {
440        match self.image_data()? {
441            ImageData::Raster(data) => Some(data),
442            _ => None,
443        }
444    }
445
446    pub fn raster_image_data_mut(&mut self) -> Option<&mut RasterImageData> {
447        match self.image_data_mut()? {
448            ImageData::Raster(data) => Some(data),
449            _ => None,
450        }
451    }
452
453    #[cfg(feature = "svg")]
454    pub fn svg_data(&self) -> Option<&usvg::Tree> {
455        match self.image_data()? {
456            ImageData::Svg(data) => Some(data),
457            _ => None,
458        }
459    }
460
461    #[cfg(feature = "svg")]
462    pub fn svg_data_mut(&mut self) -> Option<&mut usvg::Tree> {
463        match self.image_data_mut()? {
464            ImageData::Svg(data) => Some(data),
465            _ => None,
466        }
467    }
468
469    pub fn text_input_data(&self) -> Option<&TextInputData> {
470        match &self.node_specific_data {
471            NodeSpecificData::TextInput(data) => Some(data),
472            _ => None,
473        }
474    }
475
476    pub fn text_input_data_mut(&mut self) -> Option<&mut TextInputData> {
477        match &mut self.node_specific_data {
478            NodeSpecificData::TextInput(data) => Some(data),
479            _ => None,
480        }
481    }
482
483    pub fn checkbox_input_checked(&self) -> Option<bool> {
484        match self.node_specific_data {
485            NodeSpecificData::CheckboxInput(checked) => Some(checked),
486            _ => None,
487        }
488    }
489
490    pub fn checkbox_input_checked_mut(&mut self) -> Option<&mut bool> {
491        match self.node_specific_data {
492            NodeSpecificData::CheckboxInput(ref mut checked) => Some(checked),
493            _ => None,
494        }
495    }
496
497    pub fn flush_is_focussable(&mut self) {
498        let disabled: bool = self.attr_parsed(local_name!("disabled")).unwrap_or(false);
499        let tabindex: Option<i32> = self.attr_parsed(local_name!("tabindex"));
500
501        self.is_focussable = !disabled
502            && match tabindex {
503                Some(index) => index >= 0,
504                None => {
505                    // Some focusable HTML elements have a default tabindex value of 0 set under the hood by the user agent.
506                    // These elements are:
507                    //   - <a> or <area> with href attribute
508                    //   - <button>, <frame>, <iframe>, <input>, <object>, <select>, <textarea>, and SVG <a> element
509                    //   - <summary> element that provides summary for a <details> element.
510
511                    if [local_name!("a"), local_name!("area")].contains(&self.name.local) {
512                        self.attr(local_name!("href")).is_some()
513                    } else {
514                        const DEFAULT_FOCUSSABLE_ELEMENTS: [LocalName; 6] = [
515                            local_name!("button"),
516                            local_name!("input"),
517                            local_name!("select"),
518                            local_name!("textarea"),
519                            local_name!("frame"),
520                            local_name!("iframe"),
521                        ];
522                        DEFAULT_FOCUSSABLE_ELEMENTS.contains(&self.name.local)
523                    }
524                }
525            }
526    }
527
528    pub fn flush_style_attribute(&mut self, guard: &SharedRwLock, base_url: Option<Url>) {
529        self.style_attribute = self.attr(local_name!("style")).map(|style_str| {
530            let url = UrlExtraData::from(base_url.clone().unwrap_or_else(|| {
531                "data:text/css;charset=utf-8;base64,"
532                    .parse::<Url>()
533                    .unwrap()
534            }));
535
536            ServoArc::new(guard.wrap(parse_style_attribute(
537                style_str,
538                &url,
539                None,
540                QuirksMode::NoQuirks,
541                CssRuleType::Style,
542            )))
543        });
544    }
545
546    pub fn take_inline_layout(&mut self) -> Option<Box<TextLayout>> {
547        std::mem::take(&mut self.inline_layout_data)
548    }
549}
550
551#[derive(Debug, Clone, PartialEq, Default)]
552pub struct RasterImageData {
553    /// The width of the image
554    pub width: u32,
555    /// The height of the image
556    pub height: u32,
557    /// The raw image data in RGBA8 format
558    pub data: Arc<Vec<u8>>,
559}
560impl RasterImageData {
561    pub fn new(width: u32, height: u32, data: Arc<Vec<u8>>) -> Self {
562        Self {
563            width,
564            height,
565            data,
566        }
567    }
568}
569
570#[derive(Debug, Clone)]
571pub enum ImageData {
572    Raster(RasterImageData),
573    #[cfg(feature = "svg")]
574    Svg(Box<usvg::Tree>),
575    None,
576}
577#[cfg(feature = "svg")]
578impl From<usvg::Tree> for ImageData {
579    fn from(value: usvg::Tree) -> Self {
580        Self::Svg(Box::new(value))
581    }
582}
583
584#[derive(Debug, Clone, PartialEq)]
585pub enum Status {
586    Ok,
587    Error,
588    Loading,
589}
590
591#[derive(Debug, Clone)]
592pub struct BackgroundImageData {
593    /// The url of the background image
594    pub url: ServoArc<Url>,
595    /// The loading status of the background image
596    pub status: Status,
597    /// The image data
598    pub image: ImageData,
599}
600
601impl BackgroundImageData {
602    pub fn new(url: ServoArc<Url>) -> Self {
603        Self {
604            url,
605            status: Status::Loading,
606            image: ImageData::None,
607        }
608    }
609}
610
611pub struct TextInputData {
612    /// A parley TextEditor instance
613    pub editor: Box<parley::PlainEditor<TextBrush>>,
614    /// Whether the input is a singleline or multiline input
615    pub is_multiline: bool,
616}
617
618// FIXME: Implement Clone for PlainEditor
619impl Clone for TextInputData {
620    fn clone(&self) -> Self {
621        TextInputData::new(self.is_multiline)
622    }
623}
624
625impl TextInputData {
626    pub fn new(is_multiline: bool) -> Self {
627        let editor = Box::new(parley::PlainEditor::new(16.0));
628        Self {
629            editor,
630            is_multiline,
631        }
632    }
633
634    pub fn set_text(
635        &mut self,
636        font_ctx: &mut FontContext,
637        layout_ctx: &mut LayoutContext<TextBrush>,
638        text: &str,
639    ) {
640        if self.editor.text() != text {
641            self.editor.set_text(text);
642            self.editor.driver(font_ctx, layout_ctx).refresh_layout();
643        }
644    }
645}
646
647/// Heterogeneous data that depends on the element's type.
648#[derive(Clone)]
649pub enum NodeSpecificData {
650    /// The element's image content (\<img\> element's only)
651    Image(Box<ImageData>),
652    /// Pre-computed table layout data
653    TableRoot(Arc<TableContext>),
654    /// Parley text editor (text inputs)
655    TextInput(TextInputData),
656    /// Checkbox checked state
657    CheckboxInput(bool),
658    /// No data (for nodes that don't need any node-specific data)
659    None,
660}
661
662impl std::fmt::Debug for NodeSpecificData {
663    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
664        match self {
665            NodeSpecificData::Image(data) => match **data {
666                ImageData::Raster(_) => f.write_str("NodeSpecificData::Image(Raster)"),
667                #[cfg(feature = "svg")]
668                ImageData::Svg(_) => f.write_str("NodeSpecificData::Image(Svg)"),
669                ImageData::None => f.write_str("NodeSpecificData::Image(None)"),
670            },
671            NodeSpecificData::TableRoot(_) => f.write_str("NodeSpecificData::TableRoot"),
672            NodeSpecificData::TextInput(_) => f.write_str("NodeSpecificData::TextInput"),
673            NodeSpecificData::CheckboxInput(_) => f.write_str("NodeSpecificData::CheckboxInput"),
674            NodeSpecificData::None => f.write_str("NodeSpecificData::None"),
675        }
676    }
677}
678
679impl Default for NodeSpecificData {
680    fn default() -> Self {
681        Self::None
682    }
683}
684
685#[derive(Clone)]
686pub struct ListItemLayout {
687    pub marker: Marker,
688    pub position: ListItemLayoutPosition,
689}
690
691//We seperate chars from strings in order to optimise rendering - ie not needing to
692//construct a whole parley layout for simple char markers
693#[derive(Debug, PartialEq, Clone)]
694pub enum Marker {
695    Char(char),
696    String(String),
697}
698
699//Value depends on list-style-position, determining whether a seperate layout is created for it
700#[derive(Clone)]
701pub enum ListItemLayoutPosition {
702    Inside,
703    Outside(Box<parley::Layout<TextBrush>>),
704}
705
706impl std::fmt::Debug for ListItemLayout {
707    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
708        write!(f, "ListItemLayout - marker {:?}", self.marker)
709    }
710}
711
712#[derive(Debug, Clone, Default, PartialEq)]
713/// Parley Brush type for Blitz which contains a `peniko::Brush` and a Blitz node id
714pub struct TextBrush {
715    /// The node id for the span
716    pub id: usize,
717    /// Peniko brush for the span (represents text color)
718    pub brush: peniko::Brush,
719}
720
721impl TextBrush {
722    pub(crate) fn from_peniko_brush(brush: peniko::Brush) -> Self {
723        Self { id: 0, brush }
724    }
725    pub(crate) fn from_color(color: AlphaColor<Srgb>) -> Self {
726        Self::from_peniko_brush(peniko::Brush::Solid(color))
727    }
728    pub(crate) fn from_id_and_color(id: usize, color: AlphaColor<Srgb>) -> Self {
729        Self {
730            id,
731            brush: peniko::Brush::Solid(color),
732        }
733    }
734}
735
736#[derive(Clone)]
737pub struct TextLayout {
738    pub text: String,
739    pub layout: parley::layout::Layout<TextBrush>,
740}
741
742impl std::fmt::Debug for TextLayout {
743    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
744        write!(f, "TextLayout")
745    }
746}
747
748#[derive(Debug, Clone)]
749pub struct TextNodeData {
750    /// The textual content of the text node
751    pub content: String,
752}
753
754impl TextNodeData {
755    pub fn new(content: String) -> Self {
756        Self { content }
757    }
758}
759
760/*
761-> Computed styles
762-> Layout
763-----> Needs to happen only when styles are computed
764*/
765
766// type DomRefCell<T> = RefCell<T>;
767
768// pub struct DomData {
769//     // ... we can probs just get away with using the html5ever types directly. basically just using the servo dom, but without the bindings
770//     local_name: html5ever::LocalName,
771//     tag_name: html5ever::QualName,
772//     namespace: html5ever::Namespace,
773//     prefix: DomRefCell<Option<html5ever::Prefix>>,
774//     attrs: DomRefCell<Vec<Attr>>,
775//     // attrs: DomRefCell<Vec<Dom<Attr>>>,
776//     id_attribute: DomRefCell<Option<Atom>>,
777//     is: DomRefCell<Option<LocalName>>,
778//     // style_attribute: DomRefCell<Option<Arc<Locked<PropertyDeclarationBlock>>>>,
779//     // attr_list: MutNullableDom<NamedNodeMap>,
780//     // class_list: MutNullableDom<DOMTokenList>,
781//     state: Cell<ElementState>,
782// }
783
784impl Node {
785    pub fn tree(&self) -> &Slab<Node> {
786        unsafe { &*self.tree }
787    }
788
789    #[track_caller]
790    pub fn with(&self, id: usize) -> &Node {
791        self.tree().get(id).unwrap()
792    }
793
794    pub fn print_tree(&self, level: usize) {
795        println!(
796            "{} {} {:?} {} {:?}",
797            "  ".repeat(level),
798            self.id,
799            self.parent,
800            self.node_debug_str().replace('\n', ""),
801            self.children
802        );
803        // println!("{} {:?}", "  ".repeat(level), self.children);
804        for child_id in self.children.iter() {
805            let child = self.with(*child_id);
806            child.print_tree(level + 1)
807        }
808    }
809
810    // Get the index of the current node in the parents child list
811    pub fn child_index(&self) -> Option<usize> {
812        self.tree()[self.parent?]
813            .children
814            .iter()
815            .position(|id| *id == self.id)
816    }
817
818    // Get the nth node in the parents child list
819    pub fn forward(&self, n: usize) -> Option<&Node> {
820        let child_idx = self.child_index().unwrap_or(0);
821        self.tree()[self.parent?]
822            .children
823            .get(child_idx + n)
824            .map(|id| self.with(*id))
825    }
826
827    pub fn backward(&self, n: usize) -> Option<&Node> {
828        let child_idx = self.child_index().unwrap_or(0);
829        if child_idx < n {
830            return None;
831        }
832
833        self.tree()[self.parent?]
834            .children
835            .get(child_idx - n)
836            .map(|id| self.with(*id))
837    }
838
839    pub fn is_element(&self) -> bool {
840        matches!(self.data, NodeData::Element { .. })
841    }
842
843    pub fn is_anonymous(&self) -> bool {
844        matches!(self.data, NodeData::AnonymousBlock { .. })
845    }
846
847    pub fn is_text_node(&self) -> bool {
848        matches!(self.data, NodeData::Text { .. })
849    }
850
851    pub fn element_data(&self) -> Option<&ElementNodeData> {
852        match self.data {
853            NodeData::Element(ref data) => Some(data),
854            NodeData::AnonymousBlock(ref data) => Some(data),
855            _ => None,
856        }
857    }
858
859    pub fn element_data_mut(&mut self) -> Option<&mut ElementNodeData> {
860        match self.data {
861            NodeData::Element(ref mut data) => Some(data),
862            NodeData::AnonymousBlock(ref mut data) => Some(data),
863            _ => None,
864        }
865    }
866
867    pub fn text_data(&self) -> Option<&TextNodeData> {
868        match self.data {
869            NodeData::Text(ref data) => Some(data),
870            _ => None,
871        }
872    }
873
874    pub fn text_data_mut(&mut self) -> Option<&mut TextNodeData> {
875        match self.data {
876            NodeData::Text(ref mut data) => Some(data),
877            _ => None,
878        }
879    }
880
881    pub fn node_debug_str(&self) -> String {
882        let mut s = String::new();
883
884        match &self.data {
885            NodeData::Document => write!(s, "DOCUMENT"),
886            // NodeData::Doctype { name, .. } => write!(s, "DOCTYPE {name}"),
887            NodeData::Text(data) => {
888                let bytes = data.content.as_bytes();
889                write!(
890                    s,
891                    "TEXT {}",
892                    &std::str::from_utf8(bytes.split_at(10.min(bytes.len())).0)
893                        .unwrap_or("INVALID UTF8")
894                )
895            }
896            NodeData::Comment => write!(
897                s,
898                "COMMENT",
899                // &std::str::from_utf8(data.contents.as_bytes().split_at(10).0).unwrap_or("INVALID UTF8")
900            ),
901            NodeData::AnonymousBlock(_) => write!(s, "AnonymousBlock"),
902            NodeData::Element(data) => {
903                let name = &data.name;
904                let class = self.attr(local_name!("class")).unwrap_or("");
905                if !class.is_empty() {
906                    write!(
907                        s,
908                        "<{} class=\"{}\"> ({:?})",
909                        name.local, class, self.display_outer
910                    )
911                } else {
912                    write!(s, "<{}> ({:?})", name.local, self.display_outer)
913                }
914            } // NodeData::ProcessingInstruction { .. } => write!(s, "ProcessingInstruction"),
915        }
916        .unwrap();
917        s
918    }
919
920    pub fn outer_html(&self) -> String {
921        let mut output = String::new();
922        self.write_outer_html(&mut output);
923        output
924    }
925
926    pub fn write_outer_html(&self, writer: &mut String) {
927        let has_children = !self.children.is_empty();
928        let current_color = self
929            .primary_styles()
930            .map(|style| style.clone_color())
931            .map(|color| color.to_css_string());
932
933        match &self.data {
934            NodeData::Document => {}
935            NodeData::Comment => {}
936            NodeData::AnonymousBlock(_) => {}
937            // NodeData::Doctype { name, .. } => write!(s, "DOCTYPE {name}"),
938            NodeData::Text(data) => {
939                writer.push_str(data.content.as_str());
940            }
941            NodeData::Element(data) => {
942                writer.push('<');
943                writer.push_str(&data.name.local);
944
945                for attr in data.attrs() {
946                    writer.push(' ');
947                    writer.push_str(&attr.name.local);
948                    writer.push_str("=\"");
949                    #[allow(clippy::unnecessary_unwrap)] // Convert to if-let chain once stabilised
950                    if current_color.is_some() && attr.value.contains("currentColor") {
951                        writer.push_str(
952                            &attr
953                                .value
954                                .replace("currentColor", current_color.as_ref().unwrap()),
955                        );
956                    } else {
957                        writer.push_str(&attr.value);
958                    }
959                    writer.push('"');
960                }
961                if !has_children {
962                    writer.push_str(" /");
963                }
964                writer.push('>');
965
966                if has_children {
967                    for &child_id in &self.children {
968                        self.tree()[child_id].write_outer_html(writer);
969                    }
970
971                    writer.push_str("</");
972                    writer.push_str(&data.name.local);
973                    writer.push('>');
974                }
975            }
976        }
977    }
978
979    pub fn attrs(&self) -> Option<&[Attribute]> {
980        Some(&self.element_data()?.attrs)
981    }
982
983    pub fn attr(&self, name: LocalName) -> Option<&str> {
984        let attr = self.attrs()?.iter().find(|id| id.name.local == name)?;
985        Some(&attr.value)
986    }
987
988    pub fn primary_styles(&self) -> Option<AtomicRef<'_, ComputedValues>> {
989        let stylo_element_data = self.stylo_element_data.borrow();
990        if stylo_element_data
991            .as_ref()
992            .and_then(|d| d.styles.get_primary())
993            .is_some()
994        {
995            Some(AtomicRef::map(
996                stylo_element_data,
997                |data: &Option<ElementData>| -> &ComputedValues {
998                    data.as_ref().unwrap().styles.get_primary().unwrap()
999                },
1000            ))
1001        } else {
1002            None
1003        }
1004    }
1005
1006    pub fn text_content(&self) -> String {
1007        let mut out = String::new();
1008        self.write_text_content(&mut out);
1009        out
1010    }
1011
1012    fn write_text_content(&self, out: &mut String) {
1013        match &self.data {
1014            NodeData::Text(data) => {
1015                out.push_str(&data.content);
1016            }
1017            NodeData::Element(..) | NodeData::AnonymousBlock(..) => {
1018                for child_id in self.children.iter() {
1019                    self.with(*child_id).write_text_content(out);
1020                }
1021            }
1022            _ => {}
1023        }
1024    }
1025
1026    pub fn flush_style_attribute(&mut self, base_url: Option<Url>) {
1027        if let NodeData::Element(ref mut elem_data) = self.data {
1028            elem_data.flush_style_attribute(&self.guard, base_url);
1029        }
1030    }
1031
1032    pub fn order(&self) -> i32 {
1033        self.primary_styles()
1034            .map(|s| match s.pseudo() {
1035                Some(PseudoElement::Before) => i32::MIN,
1036                Some(PseudoElement::After) => i32::MAX,
1037                _ => s.clone_order(),
1038            })
1039            .unwrap_or(0)
1040    }
1041
1042    pub fn z_index(&self) -> i32 {
1043        self.primary_styles()
1044            .map(|s| s.clone_z_index().integer_or(0))
1045            .unwrap_or(0)
1046    }
1047
1048    /// Takes an (x, y) position (relative to the *parent's* top-left corner) and returns:
1049    ///    - None if the position is outside of this node's bounds
1050    ///    - Some(HitResult) if the position is within the node but doesn't match any children
1051    ///    - The result of recursively calling child.hit() on the the child element that is
1052    ///      positioned at that position if there is one.
1053    ///
1054    /// TODO: z-index
1055    /// (If multiple children are positioned at the position then a random one will be recursed into)
1056    pub fn hit(&self, x: f32, y: f32) -> Option<HitResult> {
1057        let mut x = x - self.final_layout.location.x + self.scroll_offset.x as f32;
1058        let mut y = y - self.final_layout.location.y + self.scroll_offset.y as f32;
1059
1060        let size = self.final_layout.size;
1061        let matches_self = !(x < 0.0
1062            || x > size.width + self.scroll_offset.x as f32
1063            || y < 0.0
1064            || y > size.height + self.scroll_offset.y as f32);
1065
1066        let content_size = self.final_layout.content_size;
1067        let matches_content = !(x < 0.0
1068            || x > content_size.width + self.scroll_offset.x as f32
1069            || y < 0.0
1070            || y > content_size.height + self.scroll_offset.y as f32);
1071
1072        if !matches_self && !matches_content {
1073            return None;
1074        }
1075
1076        if self.is_inline_root {
1077            let content_box_offset = taffy::Point {
1078                x: self.final_layout.padding.left + self.final_layout.border.left,
1079                y: self.final_layout.padding.top + self.final_layout.border.top,
1080            };
1081            x -= content_box_offset.x;
1082            y -= content_box_offset.y;
1083        }
1084
1085        // Call `.hit()` on each child in turn. If any return `Some` then return that value. Else return `Some(self.id).
1086        self.paint_children
1087            .borrow()
1088            .iter()
1089            .flatten()
1090            .rev()
1091            .find_map(|&i| self.with(i).hit(x, y))
1092            .or_else(|| {
1093                if self.is_inline_root {
1094                    let element_data = &self.element_data().unwrap();
1095                    let layout = &element_data.inline_layout_data.as_ref().unwrap().layout;
1096                    let scale = layout.scale();
1097
1098                    Cluster::from_point(layout, x * scale, y * scale).and_then(|(cluster, _)| {
1099                        let style_index = cluster.glyphs().next()?.style_index();
1100                        let node_id = layout.styles()[style_index].brush.id;
1101                        Some(HitResult { node_id, x, y })
1102                    })
1103                } else {
1104                    None
1105                }
1106            })
1107            .or(Some(HitResult {
1108                node_id: self.id,
1109                x,
1110                y,
1111            })
1112            .filter(|_| matches_self))
1113    }
1114
1115    /// Computes the Document-relative coordinates of the Node
1116    pub fn absolute_position(&self, x: f32, y: f32) -> taffy::Point<f32> {
1117        let x = x + self.final_layout.location.x - self.scroll_offset.x as f32;
1118        let y = y + self.final_layout.location.y - self.scroll_offset.y as f32;
1119
1120        // Recurse up the layout hierarchy
1121        self.layout_parent
1122            .get()
1123            .map(|i| self.with(i).absolute_position(x, y))
1124            .unwrap_or(taffy::Point { x, y })
1125    }
1126
1127    /// Creates a synthetic click event
1128    pub fn synthetic_click_event(&self, mods: Modifiers) -> DomEventData {
1129        DomEventData::Click(self.synthetic_click_event_data(mods))
1130    }
1131
1132    pub fn synthetic_click_event_data(&self, mods: Modifiers) -> BlitzMouseButtonEvent {
1133        let absolute_position = self.absolute_position(0.0, 0.0);
1134        let x = absolute_position.x + (self.final_layout.size.width / 2.0);
1135        let y = absolute_position.y + (self.final_layout.size.height / 2.0);
1136
1137        BlitzMouseButtonEvent {
1138            x,
1139            y,
1140            mods,
1141            button: Default::default(),
1142            buttons: Default::default(),
1143        }
1144    }
1145}
1146
1147/// It might be wrong to expose this since what does *equality* mean outside the dom?
1148impl PartialEq for Node {
1149    fn eq(&self, other: &Self) -> bool {
1150        self.id == other.id
1151    }
1152}
1153
1154impl Eq for Node {}
1155
1156impl std::fmt::Debug for Node {
1157    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1158        // FIXME: update to reflect changes to fields
1159        f.debug_struct("NodeData")
1160            .field("parent", &self.parent)
1161            .field("id", &self.id)
1162            .field("is_inline_root", &self.is_inline_root)
1163            .field("children", &self.children)
1164            .field("layout_children", &self.layout_children.borrow())
1165            // .field("style", &self.style)
1166            .field("node", &self.data)
1167            .field("stylo_element_data", &self.stylo_element_data)
1168            // .field("unrounded_layout", &self.unrounded_layout)
1169            // .field("final_layout", &self.final_layout)
1170            .finish()
1171    }
1172}