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(old_tag: &Tag, new_tag: &Tag) -> bool {
171        match (old_tag, new_tag) {
172            (Tag::Element(old_name), Tag::Element(new_name)) => old_name == new_name,
173            (Tag::Component(old_name), Tag::Component(new_name)) => old_name == new_name,
174            _ => false,
175        }
176    }
177
178    /// Compares two attribute values for equality.
179    ///
180    /// Signal values are compared by their current resolved string so that
181    /// patching is skipped when the visual output has not changed. This
182    /// prevents unnecessary DOM mutations (e.g. `input.set_value()`) that
183    /// can reset cursor position or cause visual flicker.
184    ///
185    /// Event attributes are always considered unequal to ensure that
186    /// event listeners are re-bound on every patch. This is critical
187    /// because the underlying closure may capture different signal
188    /// references after a re-render, even though the event name remains
189    /// the same. The cost is minimal — `attach_event_listener` only
190    /// updates a handler wrapper without re-registering the DOM listener.
191    fn attribute_values_equal(old_val: &AttributeValue, new_val: &AttributeValue) -> bool {
192        match (old_val, new_val) {
193            (AttributeValue::Text(old_text), AttributeValue::Text(new_text)) => {
194                old_text == new_text
195            }
196            (AttributeValue::Signal(old_signal), AttributeValue::Signal(new_signal)) => {
197                old_signal.get() == new_signal.get()
198            }
199            (AttributeValue::Event(_), AttributeValue::Event(_)) => false,
200            (AttributeValue::Dynamic(old_dyn), AttributeValue::Dynamic(new_dyn)) => {
201                old_dyn == new_dyn
202            }
203            (AttributeValue::Css(old_css), AttributeValue::Css(new_css)) => old_css == new_css,
204            _ => false,
205        }
206    }
207
208    /// Gets a child node at the given index by traversing child nodes.
209    fn get_child_node(parent: &Element, index: u32) -> Option<Node> {
210        let mut current: Option<Node> = parent.first_child();
211        let mut current_index: u32 = 0;
212        while let Some(node) = current {
213            if current_index == index {
214                return Some(node);
215            }
216            current = node.next_sibling();
217            current_index += 1;
218        }
219        None
220    }
221
222    /// Patches children of an element using a positional diff algorithm.
223    fn patch_children(
224        &mut self,
225        parent: &Element,
226        old_children: &[VirtualNode],
227        new_children: &[VirtualNode],
228    ) {
229        let old_len: usize = old_children.len();
230        let new_len: usize = new_children.len();
231        let common_len: usize = old_len.min(new_len);
232        for index in 0..common_len {
233            let old_child: &VirtualNode = &old_children[index];
234            let new_child: &VirtualNode = &new_children[index];
235            if let Some(dom_child) = Self::get_child_node(parent, index as u32) {
236                if let Some(element) = dom_child.dyn_ref::<Element>() {
237                    self.patch_node(old_child, new_child, element);
238                } else if let (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) =
239                    (old_child, new_child)
240                {
241                    if old_text.get_content() != new_text.get_content() {
242                        dom_child.set_text_content(Some(new_text.get_content()));
243                    }
244                } else {
245                    let new_dom: Node = self.create_dom_node(new_child);
246                    if let Some(parent_node) = dom_child.parent_node() {
247                        let _ = parent_node.replace_child(&new_dom, &dom_child);
248                    }
249                }
250            }
251        }
252        if new_len > old_len {
253            for new_child in new_children.iter().skip(common_len) {
254                let new_dom: Node = self.create_dom_node(new_child);
255                parent.append_child(&new_dom).unwrap();
256            }
257        } else if old_len > new_len {
258            for _ in common_len..old_len {
259                if let Some(last_child) = parent.last_child() {
260                    parent.remove_child(&last_child).unwrap();
261                }
262            }
263        }
264    }
265
266    /// Creates a real DOM node from a virtual node.
267    fn create_dom_node(&mut self, node: &VirtualNode) -> Node {
268        let document: Document = window().unwrap().document().unwrap();
269        self.create_dom_node_with_document(node, &document)
270    }
271
272    /// Creates a real DOM node using a pre-acquired document reference.
273    fn create_dom_node_with_document(&mut self, node: &VirtualNode, document: &Document) -> Node {
274        match node {
275            VirtualNode::Element {
276                tag,
277                attributes,
278                children,
279                ..
280            } => {
281                let element: Element = match tag {
282                    Tag::Element(name) => document.create_element(name).unwrap(),
283                    Tag::Component(_) => {
284                        let unwrapped: VirtualNode = self.unwrap_component(node);
285                        return self.create_dom_node_with_document(&unwrapped, document);
286                    }
287                };
288                for attr in attributes {
289                    match attr.get_value() {
290                        AttributeValue::Text(value) => {
291                            if !value.is_empty() || is_boolean_property(attr.get_name()) {
292                                set_dom_attribute_or_property(&element, attr.get_name(), value);
293                            }
294                        }
295                        AttributeValue::Signal(signal) => {
296                            let initial_value: String = signal.get();
297                            if !initial_value.is_empty() || is_boolean_property(attr.get_name()) {
298                                set_dom_attribute_or_property(
299                                    &element,
300                                    attr.get_name(),
301                                    &initial_value,
302                                );
303                            }
304                            let attr_name: String = attr.get_name().clone();
305                            let element_clone: Element = element.clone();
306                            let signal_for_sub: Signal<String> = *signal;
307                            let signal_inner: Signal<String> = signal_for_sub;
308                            signal_for_sub.replace_subscribe(move || {
309                                let new_value: String = signal_inner.get();
310                                if new_value.is_empty() && !is_boolean_property(&attr_name) {
311                                    remove_dom_attribute_or_property(&element_clone, &attr_name);
312                                } else {
313                                    set_dom_attribute_or_property(
314                                        &element_clone,
315                                        &attr_name,
316                                        &new_value,
317                                    );
318                                }
319                            });
320                        }
321                        AttributeValue::Event(handler) => {
322                            self.attach_event_listener(&element, handler);
323                        }
324                        AttributeValue::Dynamic(_) => {}
325                        AttributeValue::Css(css_class) => {
326                            css_class.inject_style();
327                            set_dom_attribute_or_property(
328                                &element,
329                                attr.get_name(),
330                                css_class.get_name(),
331                            );
332                        }
333                    }
334                }
335                for child in children {
336                    let child_node: Node = self.create_dom_node_with_document(child, document);
337                    element.append_child(&child_node).unwrap();
338                }
339                element.into()
340            }
341            VirtualNode::Text(text_node) => {
342                let text: Text = document.create_text_node(text_node.get_content());
343                if let Some(signal) = text_node.try_get_signal() {
344                    let text_clone: Text = text.clone();
345                    let signal_clone: Signal<String> = *signal;
346                    signal_clone.replace_subscribe({
347                        let signal_inner: Signal<String> = signal_clone;
348                        move || {
349                            let new_value: String = signal_inner.get();
350                            text_clone.set_text_content(Some(&new_value));
351                        }
352                    });
353                }
354                text.into()
355            }
356            VirtualNode::Fragment(children) => {
357                let fragment: Element = document.create_element("div").unwrap();
358                for child in children {
359                    let child_node: Node = self.create_dom_node_with_document(child, document);
360                    fragment.append_child(&child_node).unwrap();
361                }
362                fragment.into()
363            }
364            VirtualNode::Dynamic(dynamic_node) => {
365                let placeholder: Element = document.create_element("div").unwrap();
366                let style: &str = "display: contents;";
367                let _ = placeholder.set_attribute("style", style);
368                let dynamic_id: usize = Self::assign_dynamic_id(&placeholder);
369                let initial_dom: Node =
370                    self.setup_dynamic_node(dynamic_node, dynamic_id, &placeholder, true);
371                placeholder.append_child(&initial_dom).unwrap();
372                placeholder.into()
373            }
374            VirtualNode::Empty => document.create_text_node("").into(),
375        }
376    }
377
378    /// Initializes a DynamicNode: runs the initial render, creates a sub-renderer,
379    /// and registers the re-render closure as a `__euv_signal_update__` listener.
380    fn setup_dynamic_node(
381        &mut self,
382        dynamic_node: &DynamicNode,
383        dynamic_id: usize,
384        placeholder: &Element,
385        skip_equal: bool,
386    ) -> Node {
387        let mut hook_context: HookContext = dynamic_node.get_hook_context_value();
388        hook_context.reset_hook_index();
389        let initial_vnode: VirtualNode = with_hook_context(hook_context, || dynamic_node.render());
390        let initial_unwrapped: VirtualNode = self.unwrap_component(&initial_vnode);
391        let initial_dom: Node = self.create_dom_node(&initial_unwrapped);
392        let render_fn_addr: usize = usize::from(dynamic_node);
393        let placeholder_clone: Element = placeholder.clone();
394        let mut renderer_for_sub: Renderer = Renderer::new(placeholder_clone.clone());
395        renderer_for_sub.set_current_tree(Some(initial_unwrapped));
396        let renderer_addr: usize = Box::leak(Box::new(renderer_for_sub)) as *mut Renderer as usize;
397        let closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
398            if placeholder_clone.parent_node().is_none() {
399                return;
400            }
401            hook_context.reset_hook_index();
402            let new_vnode: VirtualNode = with_hook_context(hook_context, || {
403                let inner: &mut RenderFnInner = render_fn_addr.into();
404                (inner.render_fn)()
405            });
406            if skip_equal {
407                let renderer: &Renderer = renderer_addr.into();
408                if let Some(old_vnode) = renderer.try_get_current_tree() {
409                    let new_unwrapped: VirtualNode = Renderer::unwrap_component_static(&new_vnode);
410                    if old_vnode == &new_unwrapped {
411                        return;
412                    }
413                }
414            }
415            let renderer: &mut Renderer = renderer_addr.into();
416            renderer.render(new_vnode);
417        }));
418        register_dynamic_listener(dynamic_id, closure);
419        initial_dom
420    }
421
422    /// Recursively unwraps component nodes into their rendered output.
423    fn unwrap_component(&self, node: &VirtualNode) -> VirtualNode {
424        match node {
425            VirtualNode::Element {
426                tag: Tag::Component(_),
427                children,
428                ..
429            } => {
430                if children.len() == 1 {
431                    self.unwrap_component(&children[0])
432                } else {
433                    VirtualNode::Fragment(children.clone())
434                }
435            }
436            VirtualNode::Element {
437                tag,
438                attributes,
439                children,
440                key,
441            } => {
442                let unwrapped_children: Vec<VirtualNode> = children
443                    .iter()
444                    .map(|child| self.unwrap_component(child))
445                    .collect();
446                VirtualNode::Element {
447                    tag: tag.clone(),
448                    attributes: attributes.clone(),
449                    children: unwrapped_children,
450                    key: key.clone(),
451                }
452            }
453            VirtualNode::Fragment(children) => {
454                let unwrapped_children: Vec<VirtualNode> = children
455                    .iter()
456                    .map(|child| self.unwrap_component(child))
457                    .collect();
458                VirtualNode::Fragment(unwrapped_children)
459            }
460            other => other.clone(),
461        }
462    }
463
464    /// Static version of `unwrap_component` that does not require `&self`.
465    ///
466    /// Used inside closures where only a static method is available.
467    fn unwrap_component_static(node: &VirtualNode) -> VirtualNode {
468        match node {
469            VirtualNode::Element {
470                tag: Tag::Component(_),
471                children,
472                ..
473            } => {
474                if children.len() == 1 {
475                    Self::unwrap_component_static(&children[0])
476                } else {
477                    VirtualNode::Fragment(children.clone())
478                }
479            }
480            VirtualNode::Element {
481                tag,
482                attributes,
483                children,
484                key,
485            } => {
486                let unwrapped_children: Vec<VirtualNode> =
487                    children.iter().map(Self::unwrap_component_static).collect();
488                VirtualNode::Element {
489                    tag: tag.clone(),
490                    attributes: attributes.clone(),
491                    children: unwrapped_children,
492                    key: key.clone(),
493                }
494            }
495            VirtualNode::Fragment(children) => {
496                let unwrapped_children: Vec<VirtualNode> =
497                    children.iter().map(Self::unwrap_component_static).collect();
498                VirtualNode::Fragment(unwrapped_children)
499            }
500            other => other.clone(),
501        }
502    }
503
504    /// Assigns a new `data-euv-dynamic-id` to a newly created DynamicNode placeholder.
505    fn assign_dynamic_id(placeholder: &Element) -> usize {
506        let dynamic_id: usize = NEXT_EUV_DYNAMIC_ID.fetch_add(1, Ordering::Relaxed);
507        let _ = placeholder.set_attribute("data-euv-dynamic-id", &dynamic_id.to_string());
508        dynamic_id
509    }
510
511    /// Attaches an event listener to a DOM element.
512    fn attach_event_listener(&self, element: &Element, handler: &NativeEventHandler) {
513        let euv_id: usize = match element.get_attribute(DATA_EUV_ID) {
514            Some(id_str) => id_str.parse::<usize>().unwrap_or_else(|_| {
515                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
516                let _ = element.set_attribute(DATA_EUV_ID, &new_id.to_string());
517                new_id
518            }),
519            None => {
520                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
521                let _ = element.set_attribute(DATA_EUV_ID, &new_id.to_string());
522                new_id
523            }
524        };
525        let event_name: String = handler.get_event_name().clone();
526        let key: (usize, String) = (euv_id, event_name.clone());
527        let registry: &mut HashMap<(usize, String), HandlerEntry> = get_handler_registry();
528        if let Some(existing_ptr) = registry.get(&key) {
529            let existing: &mut HandlerSlot = (*existing_ptr as usize).into();
530            existing.set_handler(Some(handler.clone()));
531        } else {
532            let handler_slot: Box<HandlerSlot> = Box::new(HandlerSlot {
533                handler: Some(handler.clone()),
534            });
535            let handler_entry: HandlerEntry = Box::leak(handler_slot) as *mut HandlerSlot;
536            let handler_addr: usize = handler_entry as usize;
537            let closure: Closure<dyn FnMut(Event)> =
538                Closure::wrap(Box::new(move |event: Event| {
539                    let slot: &mut HandlerSlot = handler_addr.into();
540                    let active_handler: NativeEventHandler = slot.get_handler();
541                    active_handler.handle(event);
542                }));
543            element
544                .add_event_listener_with_callback(&event_name, closure.as_ref().unchecked_ref())
545                .unwrap();
546            closure.forget();
547            registry.insert(key, handler_entry);
548        }
549    }
550}
551
552/// Implementation of `From` trait for converting `usize` address into `&'static mut Renderer`.
553impl From<usize> for &'static mut Renderer {
554    /// Converts a memory address into a mutable reference to `Renderer`.
555    ///
556    /// # Arguments
557    ///
558    /// - `usize` - The memory address of the `Renderer` instance.
559    ///
560    /// # Returns
561    ///
562    /// - `&'static mut Renderer` - A mutable reference to the `Renderer` at the given address.
563    ///
564    /// # Safety
565    ///
566    /// - The address is guaranteed to be a valid `Renderer` instance
567    ///   that was previously converted from a reference and is managed by the runtime.
568    #[inline(always)]
569    fn from(address: usize) -> Self {
570        unsafe { &mut *(address as *mut Renderer) }
571    }
572}
573
574/// Implementation of `From` trait for converting `usize` address into `&'static Renderer`.
575impl From<usize> for &'static Renderer {
576    /// Converts a memory address into a reference to `Renderer`.
577    ///
578    /// # Arguments
579    ///
580    /// - `usize` - The memory address of the `Renderer` instance.
581    ///
582    /// # Returns
583    ///
584    /// - `&'static Renderer` - A reference to the `Renderer` at the given address.
585    ///
586    /// # Safety
587    ///
588    /// - The address is guaranteed to be a valid `Renderer` instance
589    ///   that was previously converted from a reference and is managed by the runtime.
590    #[inline(always)]
591    fn from(address: usize) -> Self {
592        unsafe { &*(address as *const Renderer) }
593    }
594}