Skip to main content

euv_core/renderer/render/
impl.rs

1use crate::*;
2
3/// Implementation of the virtual DOM renderer.
4impl Renderer {
5    /// Creates a new `Renderer` targeting the given root DOM element.
6    ///
7    /// # Arguments
8    ///
9    /// - `Element` - The root DOM element to render into.
10    ///
11    /// # Returns
12    ///
13    /// - `Self` - A new renderer instance.
14    pub fn new(root: Element) -> Self {
15        Renderer {
16            root,
17            current_tree: None,
18        }
19    }
20
21    /// Renders the given virtual DOM tree into the real DOM.
22    ///
23    /// # Arguments
24    ///
25    /// - `VirtualNode` - The virtual DOM tree to render.
26    pub fn render(&mut self, vnode: VirtualNode) {
27        let new_unwrapped: VirtualNode = self.unwrap_component(&vnode);
28        if let Some(old_vnode) = self.try_get_current_tree() {
29            let old_unwrapped: VirtualNode = self.unwrap_component(old_vnode);
30            self.patch_root(&old_unwrapped, &new_unwrapped);
31        } else {
32            let dom_node: Node = self.create_dom_node(&new_unwrapped);
33            while let Some(child) = self.get_root().first_child() {
34                self.get_root().remove_child(&child).unwrap();
35            }
36            self.get_root().append_child(&dom_node).unwrap();
37        }
38        self.set_current_tree(Some(vnode));
39    }
40
41    /// Patches the root DOM tree by replacing the single child of `self.root`.
42    fn patch_root(&mut self, old_node: &VirtualNode, new_node: &VirtualNode) {
43        let dom_child: Option<Node> = self.get_root().first_child();
44        let is_element: bool = if let Some(ref dom_child) = dom_child {
45            dom_child.dyn_ref::<Element>().is_some()
46        } else {
47            false
48        };
49        if is_element {
50            let element: Element = dom_child.unwrap().dyn_into::<Element>().unwrap();
51            self.patch_node(old_node, new_node, &element);
52        } else if let Some(dom_child) = dom_child {
53            let new_dom: Node = self.create_dom_node(new_node);
54            self.get_root().replace_child(&new_dom, &dom_child).unwrap();
55        } else {
56            let new_dom: Node = self.create_dom_node(new_node);
57            self.get_root().append_child(&new_dom).unwrap();
58        }
59    }
60
61    /// Patches an existing DOM node to match the new virtual node.
62    fn patch_node(
63        &mut self,
64        old_node: &VirtualNode,
65        new_node: &VirtualNode,
66        dom_element: &Element,
67    ) {
68        match (old_node, new_node) {
69            (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) => {
70                if old_text.get_content() != new_text.get_content() {
71                    dom_element.set_text_content(Some(new_text.get_content()));
72                }
73            }
74            (
75                VirtualNode::Element {
76                    tag: old_tag,
77                    attributes: old_attrs,
78                    children: old_children,
79                    key: _old_key,
80                },
81                VirtualNode::Element {
82                    tag: new_tag,
83                    attributes: new_attrs,
84                    children: new_children,
85                    key: _new_key,
86                },
87            ) => {
88                if !Self::tags_equal(old_tag, new_tag) {
89                    let new_dom: Node = self.create_dom_node(new_node);
90                    if let Some(parent) = dom_element.parent_node() {
91                        parent.replace_child(&new_dom, dom_element).unwrap();
92                    }
93                    return;
94                }
95                self.patch_attributes(dom_element, old_attrs, new_attrs);
96                self.patch_children(dom_element, old_children, new_children);
97            }
98            (VirtualNode::Fragment(old_children), VirtualNode::Fragment(new_children)) => {
99                self.patch_children(dom_element, old_children, new_children);
100            }
101            (VirtualNode::Dynamic(_), VirtualNode::Dynamic(_)) => {}
102            _ => {
103                let new_dom: Node = self.create_dom_node(new_node);
104                if let Some(parent) = dom_element.parent_node() {
105                    parent.replace_child(&new_dom, dom_element).unwrap();
106                }
107            }
108        }
109    }
110
111    /// Patches attributes of an element, adding, removing, or updating as needed.
112    fn patch_attributes(
113        &mut self,
114        element: &Element,
115        old_attrs: &[AttributeEntry],
116        new_attrs: &[AttributeEntry],
117    ) {
118        for old_attr in old_attrs {
119            let removed: bool = !new_attrs
120                .iter()
121                .any(|new_attr| new_attr.get_name() == old_attr.get_name());
122            if removed {
123                remove_dom_attribute_or_property(element, old_attr.get_name());
124            }
125        }
126        for new_attr in new_attrs {
127            let old_value: Option<&AttributeValue> = old_attrs
128                .iter()
129                .find(|old_attr| old_attr.get_name() == new_attr.get_name())
130                .map(AttributeEntry::get_value);
131            let should_set: bool = match old_value {
132                Some(old_val) => !Self::attribute_values_equal(old_val, new_attr.get_value()),
133                None => true,
134            };
135            if should_set {
136                match new_attr.get_value() {
137                    AttributeValue::Text(value) => {
138                        if value.is_empty() {
139                            remove_dom_attribute_or_property(element, new_attr.get_name());
140                        } else {
141                            set_dom_attribute_or_property(element, new_attr.get_name(), value);
142                        }
143                    }
144                    AttributeValue::Signal(signal) => {
145                        let value: String = signal.get();
146                        if value.is_empty() && !is_boolean_property(new_attr.get_name()) {
147                            remove_dom_attribute_or_property(element, new_attr.get_name());
148                        } else {
149                            set_dom_attribute_or_property(element, new_attr.get_name(), &value);
150                        }
151                    }
152                    AttributeValue::Event(handler) => {
153                        self.attach_event_listener(element, handler);
154                    }
155                    AttributeValue::Dynamic(_) => {}
156                    AttributeValue::Css(css_class) => {
157                        css_class.inject_style();
158                        set_dom_attribute_or_property(
159                            element,
160                            new_attr.get_name(),
161                            css_class.get_name(),
162                        );
163                    }
164                }
165            }
166        }
167    }
168
169    /// Compares two tags for equality.
170    fn tags_equal(a: &Tag, b: &Tag) -> bool {
171        match (a, b) {
172            (Tag::Element(a_name), Tag::Element(b_name)) => a_name == b_name,
173            (Tag::Component(a_name), Tag::Component(b_name)) => a_name == b_name,
174            _ => false,
175        }
176    }
177
178    /// Compares two attribute values for equality.
179    fn attribute_values_equal(a: &AttributeValue, b: &AttributeValue) -> bool {
180        match (a, b) {
181            (AttributeValue::Text(a_val), AttributeValue::Text(b_val)) => a_val == b_val,
182            (AttributeValue::Signal(_a_sig), AttributeValue::Signal(_b_sig)) => false,
183            (AttributeValue::Event(_a_ev), AttributeValue::Event(_b_ev)) => false,
184            (AttributeValue::Dynamic(a_dyn), AttributeValue::Dynamic(b_dyn)) => a_dyn == b_dyn,
185            (AttributeValue::Css(a_css), AttributeValue::Css(b_css)) => {
186                a_css.get_name() == b_css.get_name()
187            }
188            _ => false,
189        }
190    }
191
192    /// Gets a child node at the given index by traversing child nodes.
193    fn get_child_node(parent: &Element, index: u32) -> Option<Node> {
194        let mut current: Option<Node> = parent.first_child();
195        let mut current_index: u32 = 0;
196        while let Some(node) = current {
197            if current_index == index {
198                return Some(node);
199            }
200            current = node.next_sibling();
201            current_index += 1;
202        }
203        None
204    }
205
206    /// Patches children of an element using a positional diff algorithm.
207    fn patch_children(
208        &mut self,
209        parent: &Element,
210        old_children: &[VirtualNode],
211        new_children: &[VirtualNode],
212    ) {
213        let old_len: usize = old_children.len();
214        let new_len: usize = new_children.len();
215        let common_len: usize = old_len.min(new_len);
216        for index in 0..common_len {
217            let old_child: &VirtualNode = &old_children[index];
218            let new_child: &VirtualNode = &new_children[index];
219            if let Some(dom_child) = Self::get_child_node(parent, index as u32) {
220                if let Some(element) = dom_child.dyn_ref::<Element>() {
221                    self.patch_node(old_child, new_child, element);
222                } else if let (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) =
223                    (old_child, new_child)
224                {
225                    if old_text.get_content() != new_text.get_content() {
226                        dom_child.set_text_content(Some(new_text.get_content()));
227                    }
228                } else {
229                    let new_dom: Node = self.create_dom_node(new_child);
230                    if let Some(parent_node) = dom_child.parent_node() {
231                        let _ = parent_node.replace_child(&new_dom, &dom_child);
232                    }
233                }
234            }
235        }
236        if new_len > old_len {
237            for new_child in new_children.iter().skip(common_len) {
238                let new_dom: Node = self.create_dom_node(new_child);
239                parent.append_child(&new_dom).unwrap();
240            }
241        } else if old_len > new_len {
242            for _ in common_len..old_len {
243                if let Some(last_child) = parent.last_child() {
244                    parent.remove_child(&last_child).unwrap();
245                }
246            }
247        }
248    }
249
250    /// Creates a real DOM node from a virtual node.
251    fn create_dom_node(&mut self, node: &VirtualNode) -> Node {
252        let document: Document = window().unwrap().document().unwrap();
253        self.create_dom_node_with_document(node, &document)
254    }
255
256    /// Creates a real DOM node using a pre-acquired document reference.
257    fn create_dom_node_with_document(&mut self, node: &VirtualNode, document: &Document) -> Node {
258        match node {
259            VirtualNode::Element {
260                tag,
261                attributes,
262                children,
263                ..
264            } => {
265                let element: Element = match tag {
266                    Tag::Element(name) => document.create_element(name).unwrap(),
267                    Tag::Component(_) => {
268                        let unwrapped: VirtualNode = self.unwrap_component(node);
269                        return self.create_dom_node_with_document(&unwrapped, document);
270                    }
271                };
272                for attr in attributes {
273                    match attr.get_value() {
274                        AttributeValue::Text(value) => {
275                            if !value.is_empty() || is_boolean_property(attr.get_name()) {
276                                set_dom_attribute_or_property(&element, attr.get_name(), value);
277                            }
278                        }
279                        AttributeValue::Signal(signal) => {
280                            let initial_value: String = signal.get();
281                            if !initial_value.is_empty() || is_boolean_property(attr.get_name()) {
282                                set_dom_attribute_or_property(
283                                    &element,
284                                    attr.get_name(),
285                                    &initial_value,
286                                );
287                            }
288                            let attr_name: String = attr.get_name().clone();
289                            let element_clone: Element = element.clone();
290                            let signal_for_sub: Signal<String> = *signal;
291                            let signal_inner: Signal<String> = signal_for_sub;
292                            signal_for_sub.replace_subscribe(move || {
293                                let new_value: String = signal_inner.get();
294                                if new_value.is_empty() && !is_boolean_property(&attr_name) {
295                                    remove_dom_attribute_or_property(&element_clone, &attr_name);
296                                } else {
297                                    set_dom_attribute_or_property(
298                                        &element_clone,
299                                        &attr_name,
300                                        &new_value,
301                                    );
302                                }
303                            });
304                        }
305                        AttributeValue::Event(handler) => {
306                            self.attach_event_listener(&element, handler);
307                        }
308                        AttributeValue::Dynamic(_) => {}
309                        AttributeValue::Css(css_class) => {
310                            css_class.inject_style();
311                            set_dom_attribute_or_property(
312                                &element,
313                                attr.get_name(),
314                                css_class.get_name(),
315                            );
316                        }
317                    }
318                }
319                for child in children {
320                    let child_node: Node = self.create_dom_node_with_document(child, document);
321                    element.append_child(&child_node).unwrap();
322                }
323                element.into()
324            }
325            VirtualNode::Text(text_node) => {
326                let text: Text = document.create_text_node(text_node.get_content());
327                if let Some(signal) = text_node.try_get_signal() {
328                    let text_clone: Text = text.clone();
329                    let signal_clone: Signal<String> = *signal;
330                    signal_clone.replace_subscribe({
331                        let signal_inner: Signal<String> = signal_clone;
332                        move || {
333                            let new_value: String = signal_inner.get();
334                            text_clone.set_text_content(Some(&new_value));
335                        }
336                    });
337                }
338                text.into()
339            }
340            VirtualNode::Fragment(children) => {
341                let fragment: Element = document.create_element("div").unwrap();
342                for child in children {
343                    let child_node: Node = self.create_dom_node_with_document(child, document);
344                    fragment.append_child(&child_node).unwrap();
345                }
346                fragment.into()
347            }
348            VirtualNode::Dynamic(dynamic_node) => {
349                let placeholder: Element = document.create_element("div").unwrap();
350                let style: &str = "display: contents;";
351                let _ = placeholder.set_attribute("style", style);
352                let dynamic_id: usize = Self::assign_dynamic_id(&placeholder);
353                let initial_dom: Node =
354                    self.setup_dynamic_node(dynamic_node, dynamic_id, &placeholder, true);
355                placeholder.append_child(&initial_dom).unwrap();
356                placeholder.into()
357            }
358            VirtualNode::Empty => document.create_text_node("").into(),
359        }
360    }
361
362    /// Initializes a DynamicNode: runs the initial render, creates a sub-renderer,
363    /// and registers the re-render closure as a `__euv_signal_update__` listener.
364    fn setup_dynamic_node(
365        &mut self,
366        dynamic_node: &DynamicNode,
367        dynamic_id: usize,
368        placeholder: &Element,
369        skip_equal: bool,
370    ) -> Node {
371        let mut hook_context: HookContext = dynamic_node.get_hook_context();
372        hook_context.reset_hook_index();
373        let initial_vnode: VirtualNode = with_hook_context(hook_context, || dynamic_node.render());
374        let initial_unwrapped: VirtualNode = self.unwrap_component(&initial_vnode);
375        let initial_dom: Node = self.create_dom_node(&initial_unwrapped);
376        let render_fn_addr: usize = usize::from(dynamic_node);
377        let placeholder_clone: Element = placeholder.clone();
378        let mut renderer_for_sub: Renderer = Renderer::new(placeholder_clone.clone());
379        renderer_for_sub.set_current_tree(Some(initial_unwrapped));
380        let renderer_addr: usize = Box::leak(Box::new(renderer_for_sub)) as *mut Renderer as usize;
381        let closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
382            if placeholder_clone.parent_node().is_none() {
383                return;
384            }
385            hook_context.reset_hook_index();
386            let new_vnode: VirtualNode = with_hook_context(hook_context, || {
387                let inner: &mut RenderFnInner = render_fn_addr.into();
388                (inner.render_fn)()
389            });
390            if skip_equal {
391                let renderer: &Renderer = renderer_addr.into();
392                if let Some(old_vnode) = renderer.try_get_current_tree() {
393                    let new_unwrapped: VirtualNode = Renderer::unwrap_component_static(&new_vnode);
394                    if old_vnode == &new_unwrapped {
395                        return;
396                    }
397                }
398            }
399            let renderer: &mut Renderer = renderer_addr.into();
400            renderer.render(new_vnode);
401        }));
402        register_dynamic_listener(dynamic_id, closure);
403        initial_dom
404    }
405
406    /// Recursively unwraps component nodes into their rendered output.
407    fn unwrap_component(&self, node: &VirtualNode) -> VirtualNode {
408        match node {
409            VirtualNode::Element {
410                tag: Tag::Component(_),
411                children,
412                ..
413            } => {
414                if children.len() == 1 {
415                    self.unwrap_component(&children[0])
416                } else {
417                    VirtualNode::Fragment(children.clone())
418                }
419            }
420            VirtualNode::Element {
421                tag,
422                attributes,
423                children,
424                key,
425            } => {
426                let unwrapped_children: Vec<VirtualNode> = children
427                    .iter()
428                    .map(|child| self.unwrap_component(child))
429                    .collect();
430                VirtualNode::Element {
431                    tag: tag.clone(),
432                    attributes: attributes.clone(),
433                    children: unwrapped_children,
434                    key: key.clone(),
435                }
436            }
437            VirtualNode::Fragment(children) => {
438                let unwrapped_children: Vec<VirtualNode> = children
439                    .iter()
440                    .map(|child| self.unwrap_component(child))
441                    .collect();
442                VirtualNode::Fragment(unwrapped_children)
443            }
444            other => other.clone(),
445        }
446    }
447
448    /// Static version of `unwrap_component` that does not require `&self`.
449    ///
450    /// Used inside closures where only a static method is available.
451    fn unwrap_component_static(node: &VirtualNode) -> VirtualNode {
452        match node {
453            VirtualNode::Element {
454                tag: Tag::Component(_),
455                children,
456                ..
457            } => {
458                if children.len() == 1 {
459                    Self::unwrap_component_static(&children[0])
460                } else {
461                    VirtualNode::Fragment(children.clone())
462                }
463            }
464            VirtualNode::Element {
465                tag,
466                attributes,
467                children,
468                key,
469            } => {
470                let unwrapped_children: Vec<VirtualNode> =
471                    children.iter().map(Self::unwrap_component_static).collect();
472                VirtualNode::Element {
473                    tag: tag.clone(),
474                    attributes: attributes.clone(),
475                    children: unwrapped_children,
476                    key: key.clone(),
477                }
478            }
479            VirtualNode::Fragment(children) => {
480                let unwrapped_children: Vec<VirtualNode> =
481                    children.iter().map(Self::unwrap_component_static).collect();
482                VirtualNode::Fragment(unwrapped_children)
483            }
484            other => other.clone(),
485        }
486    }
487
488    /// Assigns a new `data-euv-dynamic-id` to a newly created DynamicNode placeholder.
489    fn assign_dynamic_id(placeholder: &Element) -> usize {
490        let dynamic_id: usize = NEXT_EUV_DYNAMIC_ID.fetch_add(1, Ordering::Relaxed);
491        let _ = placeholder.set_attribute("data-euv-dynamic-id", &dynamic_id.to_string());
492        dynamic_id
493    }
494
495    /// Attaches an event listener to a DOM element.
496    fn attach_event_listener(&self, element: &Element, handler: &NativeEventHandler) {
497        let euv_id: usize = match element.get_attribute("data-euv-id") {
498            Some(id_str) => id_str.parse::<usize>().unwrap_or_else(|_| {
499                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
500                let _ = element.set_attribute("data-euv-id", &new_id.to_string());
501                new_id
502            }),
503            None => {
504                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
505                let _ = element.set_attribute("data-euv-id", &new_id.to_string());
506                new_id
507            }
508        };
509        let event_name: String = handler.get_event_name().clone();
510        let key: (usize, String) = (euv_id, event_name.clone());
511        let registry: &mut HashMap<(usize, String), HandlerEntry> = get_handler_registry();
512        if let Some(existing_ptr) = registry.get(&key) {
513            let existing: &mut HandlerSlot = (*existing_ptr as usize).into();
514            existing.handler = Some(handler.clone());
515        } else {
516            let handler_slot: Box<HandlerSlot> = Box::new(HandlerSlot {
517                handler: Some(handler.clone()),
518            });
519            let handler_entry: HandlerEntry = Box::leak(handler_slot) as *mut HandlerSlot;
520            let handler_addr: usize = handler_entry as usize;
521            let event_name_for_closure: String = event_name.clone();
522            let closure: Closure<dyn FnMut(Event)> =
523                Closure::wrap(Box::new(move |event: Event| {
524                    let slot: &mut HandlerSlot = handler_addr.into();
525                    if let Some(active_handler) = slot.handler.as_ref() {
526                        let euv_event: NativeEvent =
527                            convert_web_event(&event, &event_name_for_closure);
528                        active_handler.handle(euv_event);
529                    }
530                    event.stop_propagation();
531                }));
532            element
533                .add_event_listener_with_callback(&event_name, closure.as_ref().unchecked_ref())
534                .unwrap();
535            closure.forget();
536            registry.insert(key, handler_entry);
537        }
538    }
539}
540
541/// Implementation of Renderer accessor methods.
542impl Renderer {
543    /// Returns a reference to the root element.
544    ///
545    /// # Returns
546    ///
547    /// - `&Element` - The root element.
548    pub(crate) fn get_root(&self) -> &Element {
549        &self.root
550    }
551
552    /// Returns a reference to the current virtual DOM tree.
553    ///
554    /// # Returns
555    ///
556    /// - `Option<&VirtualNode>` - The current tree, if any.
557    pub(crate) fn try_get_current_tree(&self) -> Option<&VirtualNode> {
558        self.current_tree.as_ref()
559    }
560
561    /// Sets the current virtual DOM tree.
562    ///
563    /// # Arguments
564    ///
565    /// - `Option<VirtualNode>` - The new current tree.
566    pub(crate) fn set_current_tree(&mut self, tree: Option<VirtualNode>) {
567        self.current_tree = tree;
568    }
569}
570
571/// Implementation of `From` trait for converting `usize` address into `&'static mut Renderer`.
572impl From<usize> for &'static mut Renderer {
573    /// Converts a memory address into a mutable reference to `Renderer`.
574    ///
575    /// # Arguments
576    ///
577    /// - `usize` - The memory address of the `Renderer` instance.
578    ///
579    /// # Returns
580    ///
581    /// - `&'static mut Renderer` - A mutable reference to the `Renderer` at the given address.
582    ///
583    /// # Safety
584    ///
585    /// - The address is guaranteed to be a valid `Renderer` instance
586    ///   that was previously converted from a reference and is managed by the runtime.
587    #[inline(always)]
588    fn from(address: usize) -> Self {
589        unsafe { &mut *(address as *mut Renderer) }
590    }
591}
592
593/// Implementation of `From` trait for converting `usize` address into `&'static Renderer`.
594impl From<usize> for &'static Renderer {
595    /// Converts a memory address into a reference to `Renderer`.
596    ///
597    /// # Arguments
598    ///
599    /// - `usize` - The memory address of the `Renderer` instance.
600    ///
601    /// # Returns
602    ///
603    /// - `&'static Renderer` - A reference to the `Renderer` at the given address.
604    ///
605    /// # Safety
606    ///
607    /// - The address is guaranteed to be a valid `Renderer` instance
608    ///   that was previously converted from a reference and is managed by the runtime.
609    #[inline(always)]
610    fn from(address: usize) -> Self {
611        unsafe { &*(address as *const Renderer) }
612    }
613}