1use std::{collections::HashMap, convert::identity, fmt};
2
3use caseless::default_caseless_match_str;
4use html_escape::encode_double_quoted_attribute;
5use indexmap::IndexMap;
6use itertools::Itertools;
7use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
8
9use super::{DryChild, LazyElementAction};
10use crate::{
11 clone,
12 dom::{
13 hydro::HydroNode,
14 private::{DomElement, EventStore, InstantiableDomElement},
15 wet::{WetElement, WetNode},
16 },
17 hydration::HydrationStats,
18 node::element::Namespace,
19 HEAD_ID_ATTRIBUTE,
20};
21
22pub struct SharedDryElement<Node> {
23 namespace: Namespace,
24 tag: String,
25 attributes: IndexMap<String, String>,
26 styles: IndexMap<String, String>,
27 children: Vec<Node>,
28 shadow_children: Vec<Node>,
29 hydrate_actions: Vec<LazyElementAction>,
30 next_sibling: Option<Node>,
31}
32
33impl<Node> SharedDryElement<Node> {
34 fn style_prop_text(&self) -> Option<String> {
35 if self.styles.is_empty() {
36 return None;
37 }
38
39 debug_assert!(!self.attributes.contains_key(STYLE_ATTR));
40
41 Some(
42 self.styles
43 .iter()
44 .map(|(name, value)| format!("{name}: {value};"))
45 .join(" "),
46 )
47 }
48}
49
50impl<Node: DryChild> SharedDryElement<Node> {
51 pub fn new(namespace: Namespace, tag: &str) -> Self {
52 Self {
53 namespace,
54 tag: tag.to_owned(),
55 attributes: IndexMap::new(),
56 styles: IndexMap::new(),
57 children: Vec::new(),
58 shadow_children: Vec::new(),
59 hydrate_actions: Vec::new(),
60 next_sibling: None,
61 }
62 }
63
64 pub fn first_child(&self) -> Option<&Node> {
65 self.children.first()
66 }
67
68 pub fn next_sibling(&self) -> Option<&Node> {
69 self.next_sibling.as_ref()
70 }
71
72 pub fn set_next_sibling(&mut self, next_sibling: Option<Node>) {
73 self.next_sibling = next_sibling;
74 }
75
76 pub fn append_child(&mut self, child: &Node) {
77 if let Some(last) = self.children.last_mut() {
78 last.set_next_sibling(Some(child));
79 }
80
81 self.children.push(child.clone());
82 }
83
84 pub fn insert_child_before(&mut self, index: usize, child: &Node, next_child: Option<&Node>) {
85 if index > 0 {
86 self.children[index - 1].set_next_sibling(Some(child));
87 }
88
89 child.set_next_sibling(next_child);
90
91 self.children.insert(index, child.clone());
92 }
93
94 pub fn replace_child(&mut self, index: usize, new_child: &Node, old_child: &Node) {
95 old_child.set_next_sibling(None);
96
97 if index > 0 {
98 self.children[index - 1].set_next_sibling(Some(new_child));
99 }
100
101 new_child.set_next_sibling(self.children.get(index + 1));
102
103 self.children[index] = new_child.clone();
104 }
105
106 pub fn remove_child(&mut self, index: usize, child: &Node) {
107 child.set_next_sibling(None);
108 if index > 0 {
109 self.children[index - 1].set_next_sibling(self.children.get(index + 1));
110 }
111
112 self.children.remove(index);
113 }
114
115 pub fn clear_children(&mut self) {
116 for child in &self.children {
117 child.set_next_sibling(None);
118 }
119
120 self.children.clear();
121 }
122
123 pub fn attach_shadow_children(&mut self, children: impl IntoIterator<Item = Node>) {
124 for child in children {
125 if let Some(previous_child) = self.shadow_children.last_mut() {
126 previous_child.set_next_sibling(Some(&child));
127 }
128
129 self.shadow_children.push(child);
130 }
131 }
132
133 pub fn add_class(&mut self, name: &str) {
134 self.attributes
135 .entry("class".to_owned())
136 .and_modify(|class| {
137 if !class.split_ascii_whitespace().any(|c| c == name) {
138 if !class.is_empty() {
139 class.push(' ');
140 }
141
142 class.push_str(name);
143 }
144 })
145 .or_insert_with(|| name.to_owned());
146 }
147
148 pub fn remove_class(&mut self, name: &str) {
149 if let Some(class) = self.attributes.get_mut("class") {
150 *class = class
151 .split_ascii_whitespace()
152 .filter(|&c| c != name)
153 .join(" ");
154 }
155 }
156
157 pub fn attribute<A>(&mut self, name: &str, value: A)
158 where
159 A: crate::attribute::Attribute,
160 {
161 assert_ne!(
162 name, "xmlns",
163 "\"xmlns\" must be set via a namespace at tag creation time"
164 );
165
166 if let Some(value) = value.text() {
167 self.attributes.insert(name.to_owned(), value.to_string());
168 } else {
169 self.attributes.shift_remove(name);
170 }
171 }
172
173 pub fn on(
174 &mut self,
175 name: &'static str,
176 f: impl FnMut(JsValue) + 'static,
177 events: &EventStore,
178 ) {
179 clone!(mut events);
180
181 self.hydrate_actions
182 .push(Box::new(move |element| element.on(name, f, &mut events)))
183 }
184
185 pub fn try_dom_element(&self) -> Option<web_sys::Element> {
186 None
187 }
188
189 pub fn style_property(&mut self, name: &str, value: &str) {
190 self.styles.insert(name.to_owned(), value.to_owned());
191 }
192
193 pub fn effect(&mut self, f: impl FnOnce(&web_sys::Element) + 'static) {
194 self.hydrate_actions
195 .push(Box::new(move |element| element.effect(f)))
196 }
197
198 pub fn observe_attributes(
199 &mut self,
200 f: impl FnMut(js_sys::Array, web_sys::MutationObserver) + 'static,
201 events: &EventStore,
202 ) {
203 clone!(mut events);
204
205 self.hydrate_actions.push(Box::new(move |element| {
206 element.observe_attributes(f, &mut events)
207 }))
208 }
209
210 pub fn clone_node(&self) -> Self {
211 Self {
212 namespace: self.namespace.clone(),
213 tag: self.tag.clone(),
214 attributes: self.attributes.clone(),
215 styles: self.styles.clone(),
216 children: Self::clone_children(&self.children),
217 shadow_children: Self::clone_children(&self.shadow_children),
218 hydrate_actions: Vec::new(),
219 next_sibling: None,
220 }
221 }
222
223 fn clone_children(children: &[Node]) -> Vec<Node> {
224 let children: Vec<Node> = children.iter().map(Node::clone_node).collect();
225
226 for (index, child) in children.iter().enumerate() {
227 child.set_next_sibling(children.get(index + 1));
228 }
229
230 children
231 }
232}
233
234impl SharedDryElement<HydroNode> {
235 pub fn hydrate_child(
236 self,
237 parent: &web_sys::Node,
238 skip_filtered: &impl Fn(Option<web_sys::Node>) -> Option<web_sys::Node>,
239 child: &web_sys::Node,
240 tracker: &mut HydrationStats,
241 ) -> WetElement {
242 clone!(mut child);
243
244 loop {
245 if let Some(elem_child) = child.dyn_ref::<web_sys::Element>() {
246 let dom_namespace = elem_child.namespace_uri().unwrap_or_default();
247 let dry_namespace = self.namespace.as_str();
248
249 if dry_namespace == dom_namespace
250 && default_caseless_match_str(&elem_child.tag_name(), &self.tag)
251 {
252 return self.hydrate_element(elem_child, tracker);
253 }
254 }
255
256 let next = skip_filtered(child.next_sibling());
257 tracker.node_removed(&child);
258 parent.remove_child(&child).unwrap_throw();
259
260 if let Some(next_child) = next {
261 child = next_child;
262 } else {
263 break;
264 }
265 }
266
267 let wet_child: WetElement = self.into();
268 let new_element = wet_child.dom_element();
269 parent.append_child(&new_element).unwrap_throw();
270 tracker.node_added(&new_element);
271
272 wet_child
273 }
274
275 pub fn hydrate(self, dom_elem: &web_sys::Element, tracker: &mut HydrationStats) -> WetElement {
276 let existing_namespace = dom_elem.namespace_uri().unwrap_or_default();
277 let new_namespace = &self.namespace;
278 let new_tag = &self.tag;
279
280 if new_namespace.as_str() == existing_namespace
281 && default_caseless_match_str(&dom_elem.tag_name(), new_tag)
282 {
283 self.hydrate_element(dom_elem, tracker)
284 } else {
285 let new_dom_elem = new_namespace.create_element(new_tag);
286
287 while let Some(child) = dom_elem.first_child() {
288 new_dom_elem.append_child(&child).unwrap_throw();
289 }
290
291 dom_elem
292 .replace_with_with_node_1(&new_dom_elem)
293 .unwrap_throw();
294 self.hydrate_element(&new_dom_elem, tracker)
295 }
296 }
297
298 pub fn hydrate_in_head(self, head: &WetElement, id: &str, tracker: &mut HydrationStats) {
299 let id = id.to_string();
300 let skip_filtered = move |mut node: Option<web_sys::Node>| {
301 while let Some(current) = node {
302 if current
303 .dyn_ref::<web_sys::Element>()
304 .is_some_and(|elem| elem.get_attribute(HEAD_ID_ATTRIBUTE).as_ref() == Some(&id))
305 {
306 return Some(current);
307 }
308
309 node = current.next_sibling();
310 }
311
312 None
313 };
314
315 Self::hydrate_children(&head.dom_element(), &skip_filtered, self.children, tracker);
316 }
317
318 fn hydrate_element(
319 self,
320 dom_elem: &web_sys::Element,
321 tracker: &mut HydrationStats,
322 ) -> WetElement {
323 self.reconcile_attributes(dom_elem, tracker);
324 let mut elem = WetElement::from_element(dom_elem.clone());
325
326 Self::hydrate_children(dom_elem, &identity, self.children, tracker);
327
328 if !self.shadow_children.is_empty() {
329 let shadow_root = elem.create_shadow_root();
330 Self::hydrate_children(&shadow_root, &identity, self.shadow_children, tracker);
331 }
332
333 for event in self.hydrate_actions {
334 event(&mut elem);
335 }
336
337 elem
338 }
339
340 fn hydrate_children(
341 dom_elem: &web_sys::Node,
342 skip_filtered: &impl Fn(Option<web_sys::Node>) -> Option<web_sys::Node>,
343 children: impl IntoIterator<Item = HydroNode>,
344 tracker: &mut HydrationStats,
345 ) {
346 let mut children = children.into_iter();
347 let mut current_child = skip_filtered(dom_elem.first_child());
348
349 for child in children.by_ref() {
350 if let Some(node) = ¤t_child {
351 let hydrated_elem = child.hydrate_child(dom_elem, skip_filtered, node, tracker);
352 current_child = skip_filtered(hydrated_elem.dom_node().next_sibling());
353 } else {
354 Self::hydrate_with_new(dom_elem, child, tracker);
355 break;
356 }
357 }
358
359 for child in children {
360 Self::hydrate_with_new(dom_elem, child, tracker);
361 }
362
363 Self::remove_children_from(dom_elem, skip_filtered, current_child, tracker);
364 }
365
366 fn remove_children_from(
368 parent: &web_sys::Node,
369 skip_filtered: &impl Fn(Option<web_sys::Node>) -> Option<web_sys::Node>,
370 mut child: Option<web_sys::Node>,
371 tracker: &mut HydrationStats,
372 ) {
373 while let Some(node) = child {
374 let next_child = skip_filtered(node.next_sibling());
375 tracker.node_removed(&node);
376 parent.remove_child(&node).unwrap_throw();
377 child = next_child;
378 }
379 }
380
381 fn hydrate_with_new(parent: &web_sys::Node, child: HydroNode, tracker: &mut HydrationStats) {
382 let child = WetNode::from(child);
383 let new_child = child.dom_node();
384 parent.append_child(new_child).unwrap_throw();
385 tracker.node_added(new_child);
386 }
387
388 fn reconcile_attributes(&self, dom_elem: &web_sys::Element, tracker: &mut HydrationStats) {
389 let dom_attributes = dom_elem.attributes();
390 let mut dom_attr_map = HashMap::new();
391
392 for item_index in 0.. {
393 if let Some(attr) = dom_attributes.item(item_index) {
394 dom_attr_map.insert(attr.name(), attr.value());
395 } else {
396 break;
397 }
398 }
399
400 for (name, value) in &self.attributes {
401 Self::set_attribute(&mut dom_attr_map, name, value, dom_elem, tracker);
402 }
403
404 if let Some(style) = self.style_prop_text() {
405 Self::set_attribute(&mut dom_attr_map, STYLE_ATTR, &style, dom_elem, tracker)
406 }
407
408 for name in dom_attr_map.into_keys() {
409 if !name.starts_with("data-silkenweb") {
410 tracker.attribute_removed(dom_elem, &name);
411 dom_elem.remove_attribute(&name).unwrap_throw();
412 }
413 }
414 }
415
416 fn set_attribute(
417 dom_attr_map: &mut HashMap<String, String>,
418 name: &str,
419 value: &str,
420 dom_elem: &web_sys::Element,
421 tracker: &mut HydrationStats,
422 ) {
423 let set_attr = if let Some(existing_value) = dom_attr_map.remove(name) {
424 value != existing_value
425 } else {
426 true
427 };
428
429 if set_attr {
430 dom_elem.set_attribute(name, value).unwrap_throw();
431 tracker.attribute_set(dom_elem, name, value);
432 }
433 }
434}
435
436impl<Node: fmt::Display> SharedDryElement<Node> {
437 #[cfg(feature = "declarative-shadow-dom")]
438 fn write_shadow_dom(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
439 if self.shadow_children.is_empty() {
440 return Ok(());
441 }
442
443 f.write_str(r#"<template shadowroot="open">"#)?;
444
445 for child in &self.shadow_children {
446 child.fmt(f)?;
447 }
448
449 f.write_str("</template>")?;
450
451 Ok(())
452 }
453
454 #[cfg(not(feature = "declarative-shadow-dom"))]
455 fn write_shadow_dom(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
456 Ok(())
457 }
458}
459
460impl<Node: fmt::Display> fmt::Display for SharedDryElement<Node> {
461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462 write!(f, "<{}", self.tag)?;
463
464 for (name, value) in &self.attributes {
465 fmt_attr(f, name, value)?;
466 }
467
468 if let Some(style) = self.style_prop_text() {
469 fmt_attr(f, STYLE_ATTR, &style)?;
470 }
471
472 f.write_str(">")?;
473
474 self.write_shadow_dom(f)?;
475
476 for child in &self.children {
477 child.fmt(f)?;
478 }
479
480 let has_children = !self.children.is_empty();
481 let requires_closing_tag = !NO_CLOSING_TAG.contains(&self.tag.as_str());
482
483 if requires_closing_tag || has_children {
484 write!(f, "</{}>", self.tag)?;
485 }
486
487 Ok(())
488 }
489}
490
491fn fmt_attr(f: &mut fmt::Formatter, name: &str, value: &str) -> Result<(), fmt::Error> {
492 write!(f, " {}=\"{}\"", name, encode_double_quoted_attribute(value))?;
493 Ok(())
494}
495
496impl<Node: Into<WetNode>> From<SharedDryElement<Node>> for WetElement {
497 fn from(dry: SharedDryElement<Node>) -> Self {
498 let mut wet = WetElement::new(&dry.namespace, &dry.tag);
499
500 if let Some(style) = dry.style_prop_text() {
501 wet.attribute(STYLE_ATTR, &style);
502 }
503
504 for (name, value) in dry.attributes {
505 wet.attribute(&name, value);
506 }
507
508 for child in dry.children {
509 wet.append_child(&child.into());
510 }
511
512 if !dry.shadow_children.is_empty() {
513 wet.attach_shadow_children(dry.shadow_children.into_iter().map(|child| child.into()));
514 }
515
516 for action in dry.hydrate_actions {
517 action(&mut wet);
518 }
519
520 wet
521 }
522}
523
524const STYLE_ATTR: &str = "style";
525
526const NO_CLOSING_TAG: &[&str] = &[
527 "area", "base", "br", "col", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
528 "source", "track", "wbr",
529];