Skip to main content

euv_core/renderer/
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                while let Some(child) = dom_element.first_child() {
87                    dom_element.remove_child(&child).unwrap();
88                }
89                let mut hook_context: HookContext = new_dynamic.get_hook_context();
90                hook_context.reset_hook_index();
91                let initial_vnode: VirtualNode = with_hook_context(hook_context, || {
92                    let mut borrowed: RefMut<dyn FnMut() -> VirtualNode> =
93                        new_dynamic.get_render_fn().borrow_mut();
94                    borrowed()
95                });
96                let initial_unwrapped: VirtualNode = self.unwrap_component(&initial_vnode);
97                let initial_dom: Node = self.create_dom_node(&initial_unwrapped);
98                dom_element.append_child(&initial_dom).unwrap();
99                let render_fn_clone: Rc<RefCell<dyn FnMut() -> VirtualNode>> =
100                    new_dynamic.get_render_fn().clone();
101                let placeholder_clone: Element = dom_element.clone();
102                let mut renderer_for_sub: Renderer = Renderer::new(placeholder_clone.clone());
103                renderer_for_sub.set_current_tree(Some(initial_unwrapped));
104                let renderer_ref: Rc<RefCell<Renderer>> = Rc::new(RefCell::new(renderer_for_sub));
105                let renderer_ref_for_sub: Rc<RefCell<Renderer>> = Rc::clone(&renderer_ref);
106                let render_fn_for_sub: Rc<RefCell<dyn FnMut() -> VirtualNode>> =
107                    Rc::clone(&render_fn_clone);
108                let window: Window = window().unwrap();
109                let re_render_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
110                    if placeholder_clone.parent_node().is_none() {
111                        return;
112                    }
113                    hook_context.reset_hook_index();
114                    let new_vnode: VirtualNode =
115                        with_hook_context(hook_context, || render_fn_for_sub.borrow_mut()());
116                    renderer_ref_for_sub.borrow_mut().render(new_vnode);
117                }));
118                window
119                    .add_event_listener_with_callback(
120                        &NativeEventName::EuvSignalUpdate.to_string(),
121                        re_render_closure.as_ref().unchecked_ref(),
122                    )
123                    .unwrap();
124                re_render_closure.forget();
125            }
126            _ => {
127                let new_dom: Node = self.create_dom_node(new_node);
128                if let Some(parent) = dom_element.parent_node() {
129                    parent.replace_child(&new_dom, dom_element).unwrap();
130                }
131            }
132        }
133    }
134
135    /// Patches attributes of an element, adding, removing, or updating as needed.
136    fn patch_attributes(
137        &mut self,
138        element: &Element,
139        old_attrs: &[AttributeEntry],
140        new_attrs: &[AttributeEntry],
141    ) {
142        let mut old_map: HashMap<&str, &AttributeValue> = HashMap::new();
143        for attr in old_attrs {
144            old_map.insert(attr.get_name(), attr.get_value());
145        }
146        let mut new_map: HashMap<&str, &AttributeValue> = HashMap::new();
147        for attr in new_attrs {
148            new_map.insert(attr.get_name(), attr.get_value());
149        }
150        for name in old_map.keys() {
151            if !new_map.contains_key(*name) {
152                Self::remove_dom_attribute_or_property(element, name);
153            }
154        }
155        for attr in new_attrs {
156            let should_set: bool = match old_map.get(attr.get_name().as_str()) {
157                Some(old_value) => !Self::attribute_values_equal(old_value, attr.get_value()),
158                None => true,
159            };
160            if should_set {
161                match attr.get_value() {
162                    AttributeValue::Text(value) => {
163                        if value.is_empty() {
164                            Self::remove_dom_attribute_or_property(element, attr.get_name());
165                        } else {
166                            Self::set_dom_attribute_or_property(element, attr.get_name(), value);
167                        }
168                    }
169                    AttributeValue::Signal(signal) => {
170                        let value: String = signal.get();
171                        if value.is_empty() && !Self::is_boolean_property(attr.get_name()) {
172                            Self::remove_dom_attribute_or_property(element, attr.get_name());
173                        } else {
174                            Self::set_dom_attribute_or_property(element, attr.get_name(), &value);
175                        }
176                    }
177                    AttributeValue::Event(handler) => {
178                        self.attach_event_listener(element, handler);
179                    }
180                    AttributeValue::Dynamic(_) => {}
181                    AttributeValue::Css(css_class) => {
182                        css_class.inject_style();
183                        Self::set_dom_attribute_or_property(
184                            element,
185                            attr.get_name(),
186                            css_class.get_name(),
187                        );
188                    }
189                }
190            }
191        }
192    }
193
194    /// Returns true if the given attribute name is a boolean attribute that
195    /// requires DOM property-based manipulation instead of HTML attribute strings.
196    fn is_boolean_property(name: &str) -> bool {
197        matches!(name, "checked" | "disabled" | "selected" | "readonly")
198    }
199
200    /// Removes or clears a DOM attribute/property, depending on the attribute name.
201    ///
202    /// For `value`, sets the DOM property to an empty string rather than calling
203    /// `remove_attribute`, because `remove_attribute("value")` only removes the
204    /// HTML attribute and does not clear the displayed value of input elements.
205    /// For boolean properties (`checked`, `disabled`, `selected`, `readonly`),
206    /// sets the DOM property to `false` rather than calling `remove_attribute`,
207    /// because `remove_attribute` on a previously-set attribute may not correctly
208    /// reset the property in all browsers.
209    fn remove_dom_attribute_or_property(element: &Element, name: &str) {
210        if name == "value" {
211            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
212                input.set_value("");
213                return;
214            }
215            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
216                textarea.set_value("");
217                return;
218            }
219            if let Some(select) = element.dyn_ref::<HtmlSelectElement>() {
220                select.set_value("");
221                return;
222            }
223        }
224        if name == "checked"
225            && let Some(input) = element.dyn_ref::<HtmlInputElement>()
226        {
227            input.set_checked(false);
228            return;
229        }
230        if name == "disabled" {
231            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
232                input.set_disabled(false);
233                return;
234            }
235            if let Some(button) = element.dyn_ref::<HtmlButtonElement>() {
236                button.set_disabled(false);
237                return;
238            }
239            if let Some(select) = element.dyn_ref::<HtmlSelectElement>() {
240                select.set_disabled(false);
241                return;
242            }
243            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
244                textarea.set_disabled(false);
245                return;
246            }
247        }
248        if name == "selected"
249            && let Some(option) = element.dyn_ref::<HtmlOptionElement>()
250        {
251            option.set_selected(false);
252            return;
253        }
254        if name == "readonly" {
255            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
256                input.set_read_only(false);
257                return;
258            }
259            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
260                textarea.set_read_only(false);
261                return;
262            }
263        }
264        let _ = element.remove_attribute(name);
265    }
266
267    /// Sets a DOM attribute or property, depending on the attribute name.
268    ///
269    /// For `value`, uses the DOM property to ensure input elements update correctly.
270    /// For boolean attributes (`checked`, `disabled`, `selected`, `readonly`),
271    /// uses the DOM property so that the browser honors the value correctly
272    /// (HTML attributes are present-or-absent, not true/false strings).
273    /// For all other attributes, uses `set_attribute`.
274    fn set_dom_attribute_or_property(element: &Element, name: &str, value: &str) {
275        if name == "value" {
276            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
277                input.set_value(value);
278                return;
279            }
280            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
281                textarea.set_value(value);
282                return;
283            }
284            if let Some(select) = element.dyn_ref::<HtmlSelectElement>() {
285                select.set_value(value);
286                return;
287            }
288        }
289        if name == "checked"
290            && let Some(input) = element.dyn_ref::<HtmlInputElement>()
291        {
292            input.set_checked(value == "true");
293            return;
294        }
295        if name == "disabled" {
296            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
297                input.set_disabled(value == "true");
298                return;
299            }
300            if let Some(button) = element.dyn_ref::<HtmlButtonElement>() {
301                button.set_disabled(value == "true");
302                return;
303            }
304            if let Some(select) = element.dyn_ref::<HtmlSelectElement>() {
305                select.set_disabled(value == "true");
306                return;
307            }
308            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
309                textarea.set_disabled(value == "true");
310                return;
311            }
312        }
313        if name == "selected"
314            && let Some(option) = element.dyn_ref::<HtmlOptionElement>()
315        {
316            option.set_selected(value == "true");
317            return;
318        }
319        if name == "readonly" {
320            if let Some(input) = element.dyn_ref::<HtmlInputElement>() {
321                input.set_read_only(value == "true");
322                return;
323            }
324            if let Some(textarea) = element.dyn_ref::<HtmlTextAreaElement>() {
325                textarea.set_read_only(value == "true");
326                return;
327            }
328        }
329        let _ = element.set_attribute(name, value);
330    }
331
332    /// Compares two tags for equality.
333    fn tags_equal(a: &Tag, b: &Tag) -> bool {
334        match (a, b) {
335            (Tag::Element(a_name), Tag::Element(b_name)) => a_name == b_name,
336            (Tag::Component(a_name), Tag::Component(b_name)) => a_name == b_name,
337            _ => false,
338        }
339    }
340
341    /// Compares two attribute values for equality.
342    ///
343    /// Event attributes are always considered unequal to ensure that
344    /// event listeners are re-bound on every patch. This is critical
345    /// because the underlying closure may capture different signal
346    /// references after a route change, even though the event name
347    /// remains the same.
348    fn attribute_values_equal(a: &AttributeValue, b: &AttributeValue) -> bool {
349        match (a, b) {
350            (AttributeValue::Text(a_val), AttributeValue::Text(b_val)) => a_val == b_val,
351            (AttributeValue::Signal(_a_sig), AttributeValue::Signal(_b_sig)) => false,
352            (AttributeValue::Event(_a_ev), AttributeValue::Event(_b_ev)) => false,
353            (AttributeValue::Dynamic(a_dyn), AttributeValue::Dynamic(b_dyn)) => a_dyn == b_dyn,
354            (AttributeValue::Css(a_css), AttributeValue::Css(b_css)) => {
355                a_css.get_name() == b_css.get_name()
356            }
357            _ => false,
358        }
359    }
360
361    /// Gets a child node at the given index by traversing child nodes.
362    fn get_child_node(parent: &Element, index: u32) -> Option<Node> {
363        let mut current: Option<Node> = parent.first_child();
364        let mut current_index: u32 = 0;
365        while let Some(node) = current {
366            if current_index == index {
367                return Some(node);
368            }
369            current = node.next_sibling();
370            current_index += 1;
371        }
372        None
373    }
374
375    /// Patches children of an element using a positional diff algorithm.
376    ///
377    /// For each position, patches the old child into the new child in-place.
378    /// Text nodes are updated by modifying their text content rather than
379    /// being replaced, which preserves any reactive signal subscriptions
380    /// already wired to the existing DOM text node.
381    /// Appends any extra new children, and removes any trailing old children.
382    fn patch_children(
383        &mut self,
384        parent: &Element,
385        old_children: &[VirtualNode],
386        new_children: &[VirtualNode],
387    ) {
388        let old_len: usize = old_children.len();
389        let new_len: usize = new_children.len();
390        let common_len: usize = old_len.min(new_len);
391        for index in 0..common_len {
392            let old_child: &VirtualNode = &old_children[index];
393            let new_child: &VirtualNode = &new_children[index];
394            if let Some(dom_child) = Self::get_child_node(parent, index as u32) {
395                if let Some(element) = dom_child.dyn_ref::<Element>() {
396                    self.patch_node(old_child, new_child, element);
397                } else if let (VirtualNode::Text(old_text), VirtualNode::Text(new_text)) =
398                    (old_child, new_child)
399                {
400                    if old_text.get_content() != new_text.get_content() {
401                        dom_child.set_text_content(Some(new_text.get_content()));
402                    }
403                } else {
404                    let new_dom: Node = self.create_dom_node(new_child);
405                    if let Some(parent_node) = dom_child.parent_node() {
406                        let _ = parent_node.replace_child(&new_dom, &dom_child);
407                    }
408                }
409            }
410        }
411        if new_len > old_len {
412            for new_child in new_children.iter().skip(common_len) {
413                let new_dom: Node = self.create_dom_node(new_child);
414                parent.append_child(&new_dom).unwrap();
415            }
416        } else if old_len > new_len {
417            for _ in common_len..old_len {
418                if let Some(last_child) = parent.last_child() {
419                    parent.remove_child(&last_child).unwrap();
420                }
421            }
422        }
423    }
424
425    /// Creates a real DOM node from a virtual node.
426    fn create_dom_node(&mut self, node: &VirtualNode) -> Node {
427        match node {
428            VirtualNode::Element {
429                tag,
430                attributes,
431                children,
432                ..
433            } => {
434                let document: Document = window().unwrap().document().unwrap();
435                let element: Element = match tag {
436                    Tag::Element(name) => document.create_element(name).unwrap(),
437                    Tag::Component(_) => {
438                        let unwrapped: VirtualNode = self.unwrap_component(node);
439                        return self.create_dom_node(&unwrapped);
440                    }
441                };
442                for attr in attributes {
443                    match attr.get_value() {
444                        AttributeValue::Text(value) => {
445                            if !value.is_empty() || Self::is_boolean_property(attr.get_name()) {
446                                Self::set_dom_attribute_or_property(
447                                    &element,
448                                    attr.get_name(),
449                                    value,
450                                );
451                            }
452                        }
453                        AttributeValue::Signal(signal) => {
454                            let initial_value: String = signal.get();
455                            if !initial_value.is_empty()
456                                || Self::is_boolean_property(attr.get_name())
457                            {
458                                Self::set_dom_attribute_or_property(
459                                    &element,
460                                    attr.get_name(),
461                                    &initial_value,
462                                );
463                            }
464                            let attr_name: String = attr.get_name().clone();
465                            let element_clone: Element = element.clone();
466                            let signal_for_sub: Signal<String> = *signal;
467                            let signal_inner: Signal<String> = signal_for_sub;
468                            signal_for_sub.subscribe(move || {
469                                let new_value: String = signal_inner.get();
470                                if new_value.is_empty() && !Self::is_boolean_property(&attr_name) {
471                                    Self::remove_dom_attribute_or_property(
472                                        &element_clone,
473                                        &attr_name,
474                                    );
475                                } else {
476                                    Self::set_dom_attribute_or_property(
477                                        &element_clone,
478                                        &attr_name,
479                                        &new_value,
480                                    );
481                                }
482                            });
483                        }
484                        AttributeValue::Event(handler) => {
485                            self.attach_event_listener(&element, handler);
486                        }
487                        AttributeValue::Dynamic(_) => {}
488                        AttributeValue::Css(css_class) => {
489                            css_class.inject_style();
490                            Self::set_dom_attribute_or_property(
491                                &element,
492                                attr.get_name(),
493                                css_class.get_name(),
494                            );
495                        }
496                    }
497                }
498                for child in children {
499                    let child_node: Node = self.create_dom_node(child);
500                    element.append_child(&child_node).unwrap();
501                }
502                element.into()
503            }
504            VirtualNode::Text(text_node) => {
505                let document: Document = window().unwrap().document().unwrap();
506                let text: Text = document.create_text_node(text_node.get_content());
507                if let Some(signal) = text_node.try_get_signal() {
508                    let text_clone: Text = text.clone();
509                    let signal_clone: Signal<String> = *signal;
510                    signal_clone.subscribe({
511                        let signal_inner: Signal<String> = signal_clone;
512                        move || {
513                            let new_value: String = signal_inner.get();
514                            text_clone.set_text_content(Some(&new_value));
515                        }
516                    });
517                }
518                text.into()
519            }
520            VirtualNode::Fragment(children) => {
521                let document: Document = window().unwrap().document().unwrap();
522                let fragment: Element = document.create_element("div").unwrap();
523                for child in children {
524                    let child_node: Node = self.create_dom_node(child);
525                    fragment.append_child(&child_node).unwrap();
526                }
527                fragment.into()
528            }
529            VirtualNode::Dynamic(dynamic_node) => {
530                let document: Document = window().unwrap().document().unwrap();
531                let placeholder: Element = document.create_element("div").unwrap();
532                let style: &str = "display: contents;";
533                let _ = placeholder.set_attribute("style", style);
534                let mut hook_context: HookContext = dynamic_node.get_hook_context();
535                hook_context.reset_hook_index();
536                let initial_vnode: VirtualNode = with_hook_context(hook_context, || {
537                    let mut borrowed: RefMut<dyn FnMut() -> VirtualNode> =
538                        dynamic_node.get_render_fn().borrow_mut();
539                    borrowed()
540                });
541                let initial_unwrapped: VirtualNode = self.unwrap_component(&initial_vnode);
542                let initial_dom: Node = self.create_dom_node(&initial_unwrapped);
543                placeholder.append_child(&initial_dom).unwrap();
544                let render_fn_clone: Rc<RefCell<dyn FnMut() -> VirtualNode>> =
545                    dynamic_node.get_render_fn().clone();
546                let placeholder_clone: Element = placeholder.clone();
547                let mut renderer_for_sub: Renderer = Renderer::new(placeholder_clone.clone());
548                renderer_for_sub.set_current_tree(Some(initial_unwrapped));
549                let renderer_ref: Rc<RefCell<Renderer>> = Rc::new(RefCell::new(renderer_for_sub));
550                let renderer_ref_for_sub: Rc<RefCell<Renderer>> = Rc::clone(&renderer_ref);
551                let render_fn_for_sub: Rc<RefCell<dyn FnMut() -> VirtualNode>> =
552                    Rc::clone(&render_fn_clone);
553                let window: Window = window().unwrap();
554                let closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
555                    if placeholder_clone.parent_node().is_none() {
556                        return;
557                    }
558                    hook_context.reset_hook_index();
559                    let new_vnode: VirtualNode =
560                        with_hook_context(hook_context, || render_fn_for_sub.borrow_mut()());
561                    {
562                        let renderer: Ref<'_, Renderer> = renderer_ref_for_sub.borrow();
563                        if let Some(old_vnode) = renderer.try_get_current_tree() {
564                            let new_unwrapped: VirtualNode = renderer.unwrap_component(&new_vnode);
565                            if old_vnode == &new_unwrapped {
566                                return;
567                            }
568                        }
569                    }
570                    renderer_ref_for_sub.borrow_mut().render(new_vnode);
571                }));
572                window
573                    .add_event_listener_with_callback(
574                        &NativeEventName::EuvSignalUpdate.to_string(),
575                        closure.as_ref().unchecked_ref(),
576                    )
577                    .unwrap();
578                closure.forget();
579                placeholder.into()
580            }
581            VirtualNode::Empty => {
582                let document: Document = window().unwrap().document().unwrap();
583                document.create_text_node("").into()
584            }
585        }
586    }
587
588    /// Recursively unwraps component nodes into their rendered output.
589    fn unwrap_component(&self, node: &VirtualNode) -> VirtualNode {
590        match node {
591            VirtualNode::Element {
592                tag: Tag::Component(_),
593                children,
594                ..
595            } => {
596                if children.len() == 1 {
597                    self.unwrap_component(&children[0])
598                } else {
599                    VirtualNode::Fragment(children.clone())
600                }
601            }
602            VirtualNode::Element {
603                tag,
604                attributes,
605                children,
606                key,
607            } => {
608                let unwrapped_children: Vec<VirtualNode> = children
609                    .iter()
610                    .map(|child| self.unwrap_component(child))
611                    .collect();
612                VirtualNode::Element {
613                    tag: tag.clone(),
614                    attributes: attributes.clone(),
615                    children: unwrapped_children,
616                    key: key.clone(),
617                }
618            }
619            VirtualNode::Fragment(children) => {
620                let unwrapped_children: Vec<VirtualNode> = children
621                    .iter()
622                    .map(|child| self.unwrap_component(child))
623                    .collect();
624                VirtualNode::Fragment(unwrapped_children)
625            }
626            other => other.clone(),
627        }
628    }
629
630    /// Attaches an event listener to a DOM element.
631    ///
632    /// Uses a global auto-incrementing ID stored as `data-euv-id` on the element
633    /// to uniquely identify it in the handler registry. This avoids the bug where
634    /// `element.as_ref() as *const JsValue as usize` returns the address of the
635    /// Rust-side temporary `JsValue` wrapper rather than a stable JS object identity,
636    /// causing different DOM elements to collide on the same key.
637    ///
638    /// On first attach, allocates a new ID, creates a wrapper
639    /// `Rc<RefCell<Option<NativeEventHandler>>>`, and registers a DOM
640    /// `addEventListener` closure that reads from it. On subsequent patches
641    /// for the same element+event, only updates the wrapper content.
642    fn attach_event_listener(&self, element: &Element, handler: &NativeEventHandler) {
643        let euv_id: usize = match element.get_attribute("data-euv-id") {
644            Some(id_str) => id_str.parse::<usize>().unwrap_or_else(|_| {
645                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
646                let _ = element.set_attribute("data-euv-id", &new_id.to_string());
647                new_id
648            }),
649            None => {
650                let new_id: usize = NEXT_EUV_ID.fetch_add(1, Ordering::Relaxed);
651                let _ = element.set_attribute("data-euv-id", &new_id.to_string());
652                new_id
653            }
654        };
655        let event_name: String = handler.get_event_name().clone();
656        let key: (usize, String) = (euv_id, event_name.clone());
657        let registry: &mut HashMap<(usize, String), Rc<RefCell<Option<NativeEventHandler>>>> =
658            get_handler_registry();
659        if let Some(existing_wrapper) = registry.get(&key) {
660            let mut wrapper: RefMut<Option<NativeEventHandler>> = existing_wrapper.borrow_mut();
661            *wrapper = Some(handler.clone());
662        } else {
663            let handler_wrapper: Rc<RefCell<Option<NativeEventHandler>>> =
664                Rc::new(RefCell::new(Some(handler.clone())));
665            let wrapper_for_closure: Rc<RefCell<Option<NativeEventHandler>>> =
666                Rc::clone(&handler_wrapper);
667            let event_name_for_closure: String = event_name.clone();
668            let closure: Closure<dyn FnMut(Event)> =
669                Closure::wrap(Box::new(move |event: Event| {
670                    if let Some(active_handler) = wrapper_for_closure.borrow_mut().as_ref() {
671                        let euv_event: NativeEvent =
672                            convert_web_event(&event, &event_name_for_closure);
673                        active_handler.handle(euv_event);
674                    }
675                    event.stop_propagation();
676                }));
677            element
678                .add_event_listener_with_callback(&event_name, closure.as_ref().unchecked_ref())
679                .unwrap();
680            closure.forget();
681            registry.insert(key, handler_wrapper);
682        }
683    }
684}