Skip to main content

euv_core/renderer/render/
impl.rs

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