1use std::{
2 cell::RefCell,
3 rc::{Rc, Weak},
4 thread_local,
5};
6
7use stardom_nodes::{EventKey, Node, NodeType};
8use wasm_bindgen::{intern, prelude::*};
9
10thread_local! {
11 static DOCUMENT: web_sys::Document = web_sys::window()
12 .unwrap()
13 .document()
14 .unwrap();
15}
16
17pub fn document() -> web_sys::Document {
18 DOCUMENT.with(Clone::clone)
19}
20
21#[derive(Clone, Debug)]
22pub struct DomNode(Rc<Inner>);
23
24type WeakNode = Weak<Inner>;
25type EventClosure = Closure<dyn Fn(web_sys::Event)>;
26
27#[derive(Debug)]
28struct Inner {
29 native: web_sys::Node,
30 ty: NodeType,
31
32 parent: RefCell<Option<WeakNode>>,
33 children: RefCell<Vec<DomNode>>,
34 events: RefCell<Vec<EventClosure>>,
35}
36
37impl DomNode {
38 fn new(native: web_sys::Node, ty: NodeType) -> Self {
39 Self(Rc::new(Inner {
40 native,
41 ty,
42 parent: RefCell::default(),
43 children: RefCell::default(),
44 events: RefCell::default(),
45 }))
46 }
47
48 pub fn native(&self) -> &web_sys::Node {
49 &self.0.native
50 }
51
52 fn is_virtual(&self) -> bool {
53 matches!(self.ty(), NodeType::Fragment | NodeType::Raw)
54 }
55
56 fn from_native(native: web_sys::Node) -> Option<Self> {
57 let ty = if native.has_type::<web_sys::Element>() {
58 NodeType::Element
59 } else if native.has_type::<web_sys::Text>() {
60 NodeType::Text
61 } else if native.has_type::<web_sys::Comment>() {
62 NodeType::Fragment
63 } else {
64 return None;
65 };
66
67 Some(Self::new(native, ty))
68 }
69
70 fn native_parent(&self) -> Option<web_sys::Node> {
71 self.0.native.parent_node()
72 }
73
74 fn native_target(&self) -> Option<web_sys::Node> {
75 if self.is_virtual() {
76 self.native_parent()
77 } else {
78 Some(self.0.native.clone())
79 }
80 }
81
82 fn first_node(&self) -> web_sys::Node {
83 if self.is_virtual() {
84 let children = self.0.children.borrow();
85 if let Some(first) = children.first() {
86 return first.0.native.clone();
87 }
88 }
89
90 self.0.native.clone()
91 }
92
93 pub fn mount_to_native(&self, target: &web_sys::Node, before: Option<&web_sys::Node>) {
94 if self.is_virtual() {
95 let children = self.0.children.borrow();
96 for child in &*children {
97 child.mount_to_native(target, before);
98 }
99 }
100
101 target.insert_before(&self.0.native, before).unwrap();
102 }
103
104 pub fn remove_from_native(&self, target: &web_sys::Node) {
105 if self.is_virtual() {
106 let children = self.0.children.borrow();
107 for child in &*children {
108 child.remove_from_native(target);
109 }
110 }
111
112 target.remove_child(&self.0.native).unwrap();
113 }
114}
115
116impl Node for DomNode {
117 fn element(namespace: Option<&str>, name: &str) -> Self {
118 let native = DOCUMENT
119 .with(|document| {
120 if namespace.is_some() {
121 document.create_element_ns(namespace, name)
122 } else {
123 document.create_element(name)
124 }
125 })
126 .unwrap();
127
128 Self::new(native.unchecked_into(), NodeType::Element)
129 }
130
131 fn text() -> Self {
132 let native = web_sys::Text::new().unwrap();
133
134 Self::new(native.unchecked_into(), NodeType::Text)
135 }
136
137 fn fragment() -> Self {
138 let native = web_sys::Comment::new().unwrap();
139
140 Self::new(native.unchecked_into(), NodeType::Fragment)
141 }
142
143 fn raw() -> Self {
144 let native = web_sys::Comment::new().unwrap();
145
146 Self::new(native.unchecked_into(), NodeType::Raw)
147 }
148
149 fn ty(&self) -> NodeType {
150 self.0.ty
151 }
152
153 fn parent(&self) -> Option<Self> {
154 self.0
155 .parent
156 .borrow()
157 .as_ref()
158 .and_then(Weak::upgrade)
159 .map(DomNode)
160 }
161
162 fn children(&self) -> Vec<Self> {
163 self.0.children.borrow().clone()
164 }
165
166 fn next_sibling(&self) -> Option<Self> {
167 let parent = self.parent()?;
168 let children = parent.0.children.borrow();
169 children
170 .iter()
171 .position(|node| node == self)
172 .and_then(|idx| children.get(idx + 1).cloned())
173 }
174
175 fn insert(&self, child: &Self, before: Option<&Self>) {
176 let mut children = self.0.children.borrow_mut();
177 let idx = if let Some(before) = before {
178 children
179 .iter()
180 .position(|node| node == before)
181 .expect("not a parent of insertion point node")
182 } else {
183 children.len()
184 };
185 children.insert(idx, child.clone());
186
187 child.0.parent.borrow_mut().replace(Rc::downgrade(&self.0));
188
189 if let Some(target) = self.native_target() {
190 let before = before.map(|node| node.first_node()).or_else(|| {
191 if self.is_virtual() {
192 self.native().next_sibling()
193 } else {
194 None
195 }
196 });
197
198 child.mount_to_native(&target, before.as_ref());
199 }
200 }
201
202 fn remove(&self, child: &Self) {
203 let mut children = self.0.children.borrow_mut();
204 let idx = children
205 .iter()
206 .position(|node| node == child)
207 .expect("not a parent of child node");
208 children.remove(idx);
209
210 child.0.parent.borrow_mut().take();
211
212 if let Some(target) = self.native_target() {
213 child.remove_from_native(&target);
214 }
215 }
216
217 fn set_text(&self, content: &str) {
218 match self.ty() {
219 NodeType::Text => {
220 self.0.native.set_text_content(Some(content));
221 }
222 NodeType::Raw => {
223 for child in self.0.children.borrow().clone() {
224 self.remove(&child);
225 }
226
227 let range = web_sys::Range::new().unwrap();
228 let doc = range.create_contextual_fragment(content).unwrap();
229 let native_nodes = doc.child_nodes();
230
231 for i in 0..native_nodes.length() {
232 let native = native_nodes.get(i).unwrap();
233 if let Some(node) = Self::from_native(native) {
234 self.insert(&node, None);
235 }
236 }
237 }
238 _ => panic!("can only set text content of text or raw nodes"),
239 }
240 }
241
242 fn attr(&self, name: &str) -> Option<String> {
243 if self.ty() == NodeType::Element {
244 self.0
245 .native
246 .unchecked_ref::<web_sys::Element>()
247 .get_attribute(name)
248 } else {
249 panic!("attributes only exist on element nodes");
250 }
251 }
252
253 fn set_attr(&self, name: &str, value: &str) {
254 if self.ty() == NodeType::Element {
255 self.0
256 .native
257 .unchecked_ref::<web_sys::Element>()
258 .set_attribute(intern(name), value)
259 .unwrap();
260 } else {
261 panic!("attributes only exist on element nodes");
262 }
263 }
264
265 fn remove_attr(&self, name: &str) {
266 if self.ty() == NodeType::Element {
267 self.0
268 .native
269 .unchecked_ref::<web_sys::Element>()
270 .remove_attribute(intern(name))
271 .unwrap();
272 } else {
273 panic!("attributes only exist on element nodes");
274 }
275 }
276
277 fn event<E, F>(&self, event: &E, f: F)
278 where
279 E: EventKey,
280 F: Fn(E::Event) + 'static,
281 {
282 if self.ty() != NodeType::Element {
283 panic!("can only set events on element nodes");
284 }
285
286 let closure = EventClosure::new(move |value: web_sys::Event| {
287 let value = value
288 .dyn_into::<E::Event>()
289 .expect("invalid event type cast");
290 f(value);
291 });
292
293 self.0
294 .native
295 .add_event_listener_with_callback(
296 intern(event.name()),
297 closure.as_ref().unchecked_ref(),
298 )
299 .unwrap();
300
301 self.0.events.borrow_mut().push(closure);
302 }
303}
304
305impl PartialEq for DomNode {
306 fn eq(&self, other: &Self) -> bool {
307 Rc::ptr_eq(&self.0, &other.0)
308 }
309}
310
311impl Eq for DomNode {}
312
313#[cfg(all(test, target_family = "wasm"))]
314mod tests {
315 use crate::{DomNode as N, Node};
316 use wasm_bindgen_test::*;
317
318 wasm_bindgen_test_configure!(run_in_browser);
319
320 #[wasm_bindgen_test]
321 fn fragment_insertion() {
322 let root = N::element(None, "div");
323
324 let last = N::text();
325 root.insert(&last, None);
326
327 let outer = N::fragment();
328 let inner = N::text();
329
330 root.insert(&outer, Some(&last));
331 outer.insert(&inner, None);
332
333 assert_eq!(inner.native().next_sibling(), Some(last.native().clone()));
334 }
335}