virtual_node/
create_element.rs

1use js_sys::Reflect;
2use std::cell::RefCell;
3use std::rc::Rc;
4use wasm_bindgen::JsValue;
5use web_sys::{Document, Element};
6
7use crate::event::{VirtualEventElement, VirtualEvents};
8use crate::{AttributeValue, VElement, VirtualEventNode, VirtualNode};
9
10mod add_events;
11
12// Used to indicate that a DOM node was created from a virtual-node.
13#[doc(hidden)]
14pub const VIRTUAL_NODE_MARKER_PROPERTY: &'static str = "__v__";
15
16impl VElement {
17    /// Build a DOM element by recursively creating DOM nodes for this element and it's
18    /// children, it's children's children, etc.
19    pub(crate) fn create_element_node(
20        &self,
21        events: &mut VirtualEvents,
22    ) -> (Element, VirtualEventNode) {
23        let document = web_sys::window().unwrap().document().unwrap();
24
25        let element = if html_validation::is_svg_namespace(&self.tag) {
26            document
27                .create_element_ns(Some("http://www.w3.org/2000/svg"), &self.tag)
28                .unwrap()
29        } else {
30            document.create_element(&self.tag).unwrap()
31        };
32        set_virtual_node_marker(&element);
33
34        self.attrs.iter().for_each(|(name, value)| {
35            match value {
36                AttributeValue::String(s) => {
37                    element.set_attribute(name, s).unwrap();
38                }
39                AttributeValue::Bool(b) => {
40                    if *b {
41                        element.set_attribute(name, "").unwrap();
42                    }
43                }
44            };
45        });
46
47        let mut event_elem = events.create_element_node();
48        self.add_events(
49            &element,
50            events,
51            event_elem.as_element().unwrap().events_id(),
52        );
53
54        self.append_children_to_dom(
55            &element,
56            &document,
57            event_elem.as_element_mut().unwrap(),
58            events,
59        );
60
61        self.special_attributes
62            .maybe_call_on_create_element(&element);
63
64        if let Some(inner_html) = &self.special_attributes.dangerous_inner_html {
65            element.set_inner_html(inner_html);
66        }
67
68        (element, event_elem)
69    }
70}
71
72impl VElement {
73    fn append_children_to_dom(
74        &self,
75        element: &Element,
76        document: &Document,
77        event_node: &mut VirtualEventElement,
78        events: &mut VirtualEvents,
79    ) {
80        let mut previous_node_was_text = false;
81
82        self.children.iter().for_each(|child| {
83            let child_events_node = match child {
84                VirtualNode::Text(text_node) => {
85                    let current_node = element.as_ref() as &web_sys::Node;
86
87                    // We ensure that the text siblings are patched by preventing the browser from merging
88                    // neighboring text nodes. Originally inspired by some of React's work from 2016.
89                    //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
90                    //  -> https://github.com/facebook/react/pull/5753
91                    //
92                    // `ptns` = Percy text node separator
93                    if previous_node_was_text {
94                        let separator = document.create_comment("ptns");
95                        set_virtual_node_marker(&separator);
96                        current_node
97                            .append_child(separator.as_ref() as &web_sys::Node)
98                            .unwrap();
99                    }
100
101                    current_node
102                        .append_child(&text_node.create_text_node())
103                        .unwrap();
104
105                    previous_node_was_text = true;
106
107                    events.create_text_node()
108                }
109                VirtualNode::Element(element_node) => {
110                    previous_node_was_text = false;
111
112                    let (child, child_events) = element_node.create_element_node(events);
113                    let child_elem: Element = child;
114
115                    element.append_child(&child_elem).unwrap();
116
117                    child_events
118                }
119            };
120
121            let child_events_node = Rc::new(RefCell::new(child_events_node));
122            event_node.append_child(child_events_node.clone());
123        });
124    }
125}
126
127/// Set a property on a node that can be used to know if a node was created by Percy.
128pub(crate) fn set_virtual_node_marker(node: &JsValue) {
129    let unused_data = 123;
130
131    Reflect::set(
132        &node.into(),
133        &VIRTUAL_NODE_MARKER_PROPERTY.into(),
134        &unused_data.into(),
135    )
136    .unwrap();
137}