1#![deny(unsafe_code)]
21
22use engine_observables_api::{DomArenaStats, DomNodeKindStats};
23use layout_dom_api::{
24 AttributeView, DomMutation, LayoutDom, LayoutDomMut, LocalName, Namespace, NodeKind, QualName,
25};
26use serval_static_dom::{StaticDocument, StaticNodeId};
27
28mod forms;
29mod serialize;
30
31#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
37pub struct NodeId(usize);
38
39impl NodeId {
40 pub fn raw(self) -> usize {
43 self.0
44 }
45
46 pub fn from_raw(raw: usize) -> Self {
48 Self(raw)
49 }
50}
51
52#[cfg(all(debug_assertions, target_pointer_width = "64"))]
70mod fence {
71 pub const INDEX_BITS: u32 = 48;
74 pub const INDEX_MASK: usize = (1usize << INDEX_BITS) - 1;
75 pub const TAG_MASK: u64 = (1u64 << (64 - INDEX_BITS)) - 1;
79
80 pub fn next_doc_tag() -> u64 {
82 use std::sync::atomic::{AtomicU64, Ordering};
83 static COUNTER: AtomicU64 = AtomicU64::new(1);
84 COUNTER.fetch_add(1, Ordering::Relaxed) & TAG_MASK
85 }
86}
87
88#[derive(Default)]
94struct FnvHasher(u64);
95
96impl std::hash::Hasher for FnvHasher {
97 fn finish(&self) -> u64 {
98 self.0
99 }
100 fn write(&mut self, bytes: &[u8]) {
101 let mut h = if self.0 == 0 {
102 0xcbf2_9ce4_8422_2325
103 } else {
104 self.0
105 };
106 for &b in bytes {
107 h ^= b as u64;
108 h = h.wrapping_mul(0x0000_0100_0000_01b3);
109 }
110 self.0 = h;
111 }
112}
113
114type NodeStore = std::collections::HashMap<usize, Node, std::hash::BuildHasherDefault<FnvHasher>>;
119
120struct Node {
121 kind: NodeKind,
122 name: Option<QualName>,
123 attrs: Vec<(QualName, String)>,
124 text: Option<String>,
125 parent: Option<NodeId>,
126 children: Vec<NodeId>,
127}
128
129impl Node {
130 fn new(kind: NodeKind) -> Self {
131 Self {
132 kind,
133 name: None,
134 attrs: Vec::new(),
135 text: None,
136 parent: None,
137 children: Vec::new(),
138 }
139 }
140}
141
142pub struct ScriptedDom {
147 nodes: NodeStore,
148 next_id: usize,
151 root: NodeId,
156 mutations: Vec<DomMutation<NodeId>>,
157 #[cfg(all(debug_assertions, target_pointer_width = "64"))]
160 doc_tag: u64,
161}
162
163impl Default for ScriptedDom {
164 fn default() -> Self {
165 Self::new()
166 }
167}
168
169#[derive(Debug, Default, Clone)]
178pub struct Pins {
179 pinned: std::collections::HashSet<NodeId>,
180}
181
182impl Pins {
183 pub fn new() -> Self {
185 Self::default()
186 }
187
188 pub fn pin(&mut self, id: NodeId) {
190 self.pinned.insert(id);
191 }
192
193 pub fn unpin(&mut self, id: NodeId) -> bool {
196 self.pinned.remove(&id)
197 }
198
199 pub fn retire_dead(&mut self, dead: impl IntoIterator<Item = NodeId>) -> usize {
203 dead.into_iter().filter(|id| self.pinned.remove(id)).count()
204 }
205
206 pub fn is_pinned(&self, id: NodeId) -> bool {
208 self.pinned.contains(&id)
209 }
210
211 pub fn iter(&self) -> impl Iterator<Item = NodeId> + '_ {
213 self.pinned.iter().copied()
214 }
215
216 pub fn len(&self) -> usize {
218 self.pinned.len()
219 }
220
221 pub fn is_empty(&self) -> bool {
223 self.pinned.is_empty()
224 }
225
226 pub fn clear(&mut self) {
228 self.pinned.clear();
229 }
230}
231
232impl ScriptedDom {
233 pub fn new() -> Self {
235 let mut dom = Self {
236 nodes: NodeStore::default(),
237 next_id: 0,
238 root: NodeId(0),
241 mutations: Vec::new(),
242 #[cfg(all(debug_assertions, target_pointer_width = "64"))]
243 doc_tag: fence::next_doc_tag(),
244 };
245 dom.root = dom.push(Node::new(NodeKind::Document));
246 dom
247 }
248
249 #[cfg(all(debug_assertions, target_pointer_width = "64"))]
252 fn pack(&self, value: usize) -> NodeId {
253 debug_assert!(value <= fence::INDEX_MASK, "scripted-dom node id overflow");
254 NodeId((((self.doc_tag & fence::TAG_MASK) << fence::INDEX_BITS) as usize) | value)
255 }
256 #[cfg(not(all(debug_assertions, target_pointer_width = "64")))]
257 fn pack(&self, value: usize) -> NodeId {
258 NodeId(value)
259 }
260
261 #[cfg(all(debug_assertions, target_pointer_width = "64"))]
265 fn index(&self, id: NodeId) -> usize {
266 debug_assert!(
267 (id.0 >> fence::INDEX_BITS) as u64 == (self.doc_tag & fence::TAG_MASK),
268 "NodeId from a different document (id tag {}, this doc {})",
269 (id.0 >> fence::INDEX_BITS) as u64,
270 self.doc_tag & fence::TAG_MASK,
271 );
272 id.0 & fence::INDEX_MASK
273 }
274 #[cfg(not(all(debug_assertions, target_pointer_width = "64")))]
275 #[inline]
276 fn index(&self, id: NodeId) -> usize {
277 id.0
278 }
279
280 #[cfg(all(debug_assertions, target_pointer_width = "64"))]
284 fn try_index(&self, id: NodeId) -> Option<usize> {
285 if (id.0 >> fence::INDEX_BITS) as u64 == (self.doc_tag & fence::TAG_MASK) {
286 Some(id.0 & fence::INDEX_MASK)
287 } else {
288 None
289 }
290 }
291 #[cfg(not(all(debug_assertions, target_pointer_width = "64")))]
292 #[inline]
293 fn try_index(&self, id: NodeId) -> Option<usize> {
294 Some(id.0)
295 }
296
297 pub fn capture_node_id(&self, id: NodeId) -> u64 {
300 self.index(id) as u64
301 }
302
303 pub fn remint_node_id(&self, raw: u64) -> NodeId {
306 self.pack(usize::try_from(raw).expect("captured node id must fit in usize"))
307 }
308
309 pub fn remove_child(&mut self, child: NodeId) {
315 let former_parent = self.node(child).parent;
316 self.detach(child);
317 if let Some(former_parent) = former_parent {
318 self.mutations.push(DomMutation::Removed {
319 node: child,
320 former_parent,
321 });
322 }
323 }
324
325 pub fn create_document(&mut self) -> NodeId {
332 self.push(Node::new(NodeKind::Document))
333 }
334
335 pub fn create_comment(&mut self, data: &str) -> NodeId {
337 let mut node = Node::new(NodeKind::Comment);
338 node.text = Some(data.to_owned());
339 self.push(node)
340 }
341
342 pub fn create_fragment(&mut self) -> NodeId {
344 self.push(Node::new(NodeKind::DocumentFragment))
345 }
346
347 pub fn from_serialized_document(html: &str) -> Self {
350 let src = StaticDocument::parse(html);
351 let mut dom = Self::new();
352 let children: Vec<StaticNodeId> = src.dom_children(src.document()).collect();
353 for child in children {
354 let copied = dom.copy_fragment_node(&src, child);
355 dom.attach_silent(dom.root, copied);
356 }
357 dom.mutations.clear();
358 dom
359 }
360
361 pub fn import_serialized_subtree(&mut self, html: &str) -> Result<NodeId, String> {
363 let fragment = StaticDocument::parse(html);
364 let mut candidates = if let Some(body) = Self::fragment_body(&fragment) {
365 let body_children: Vec<StaticNodeId> = fragment.dom_children(body).collect();
366 if !body_children.is_empty() {
367 body_children
368 } else {
369 Vec::new()
370 }
371 } else {
372 Vec::new()
373 };
374 if candidates.is_empty() {
375 candidates = fragment
376 .dom_children(fragment.document())
377 .filter(|&child| {
378 !fragment
379 .element_name(child)
380 .is_some_and(|q| q.local == LocalName::from("html"))
381 })
382 .collect();
383 }
384 if candidates.len() != 1 {
385 return Err(format!(
386 "serialized subtree must produce exactly one top-level node, got {}",
387 candidates.len()
388 ));
389 }
390 Ok(self.copy_fragment_node(&fragment, candidates[0]))
391 }
392
393 fn node(&self, id: NodeId) -> &Node {
394 self.nodes
395 .get(&self.index(id))
396 .expect("NodeId refers to a live node")
397 }
398
399 fn node_mut(&mut self, id: NodeId) -> &mut Node {
400 let i = self.index(id);
401 self.nodes
402 .get_mut(&i)
403 .expect("NodeId refers to a live node")
404 }
405
406 fn push(&mut self, node: Node) -> NodeId {
407 let value = self.next_id;
408 self.next_id += 1;
409 self.nodes.insert(value, node);
410 self.pack(value)
411 }
412
413 fn sibling(&self, id: NodeId, delta: isize) -> Option<NodeId> {
414 let parent = self.node(id).parent?;
415 let kids = &self.node(parent).children;
416 let pos = kids.iter().position(|&c| c == id)?;
417 let target = pos as isize + delta;
418 if target < 0 {
419 return None;
420 }
421 kids.get(target as usize).copied()
422 }
423
424 fn detach(&mut self, child: NodeId) {
426 if let Some(parent) = self.node(child).parent {
427 let kids = &mut self.node_mut(parent).children;
428 if let Some(pos) = kids.iter().position(|&c| c == child) {
429 kids.remove(pos);
430 }
431 }
432 self.node_mut(child).parent = None;
433 }
434
435 fn attach_at(&mut self, parent: NodeId, child: NodeId, reference: Option<NodeId>) {
442 self.detach(child);
443 self.node_mut(child).parent = Some(parent);
444 let idx = reference.and_then(|r| self.node(parent).children.iter().position(|&c| c == r));
445 let kids = &mut self.node_mut(parent).children;
446 match idx {
447 Some(i) => kids.insert(i, child),
448 None => kids.push(child),
449 }
450 }
451
452 fn drop_subtree(&mut self, node: NodeId) {
454 let children = std::mem::take(&mut self.node_mut(node).children);
455 for child in children {
456 self.drop_subtree(child);
457 }
458 let i = self.index(node);
459 self.nodes.remove(&i);
460 }
461
462 pub fn collect(&mut self, extra_roots: impl IntoIterator<Item = NodeId>) -> usize {
474 let mut marked: std::collections::HashSet<usize> = std::collections::HashSet::new();
477 let mut stack: Vec<usize> = Vec::new();
478 self.seed_mark(self.root, &mut marked, &mut stack);
479 for r in extra_roots {
480 self.seed_mark(r, &mut marked, &mut stack);
481 }
482 while let Some(v) = stack.pop() {
483 let neighbours: Vec<NodeId> = match self.nodes.get(&v) {
486 Some(node) => node
487 .parent
488 .into_iter()
489 .chain(node.children.iter().copied())
490 .collect(),
491 None => continue,
492 };
493 for nbr in neighbours {
494 self.seed_mark(nbr, &mut marked, &mut stack);
495 }
496 }
497
498 let before = self.nodes.len();
500 self.nodes.retain(|k, _| marked.contains(k));
501 before - self.nodes.len()
502 }
503
504 pub fn live_node_count(&self) -> usize {
508 self.nodes.len()
509 }
510
511 pub fn stats(&self) -> DomArenaStats {
513 let mut node_kinds = DomNodeKindStats::default();
514 let mut attribute_count = 0usize;
515 let mut estimated_bytes = std::mem::size_of::<Self>()
516 + self.nodes.capacity() * (std::mem::size_of::<usize>() + std::mem::size_of::<Node>())
517 + self.mutations.capacity() * std::mem::size_of::<DomMutation<NodeId>>();
518
519 for node in self.nodes.values() {
520 match node.kind {
521 NodeKind::Document => node_kinds.documents += 1,
522 NodeKind::DocumentFragment => node_kinds.document_fragments += 1,
523 NodeKind::Doctype => node_kinds.doctypes += 1,
524 NodeKind::Element => node_kinds.elements += 1,
525 NodeKind::Text => node_kinds.text += 1,
526 NodeKind::Comment => node_kinds.comments += 1,
527 NodeKind::ProcessingInstruction => node_kinds.processing_instructions += 1,
528 }
529
530 attribute_count += node.attrs.len();
531 estimated_bytes += node.attrs.capacity() * std::mem::size_of::<(QualName, String)>();
532 estimated_bytes += node.children.capacity() * std::mem::size_of::<NodeId>();
533 estimated_bytes += node.name.as_ref().map_or(0, qual_name_bytes);
534 estimated_bytes += node.text.as_ref().map_or(0, String::capacity);
535 for (name, value) in &node.attrs {
536 estimated_bytes += qual_name_bytes(name);
537 estimated_bytes += value.capacity();
538 }
539 }
540
541 DomArenaStats {
542 live_nodes: self.nodes.len(),
543 node_kinds,
544 attribute_count,
545 estimated_bytes,
546 }
547 }
548
549 fn seed_mark(
552 &self,
553 id: NodeId,
554 marked: &mut std::collections::HashSet<usize>,
555 stack: &mut Vec<usize>,
556 ) {
557 if let Some(v) = self.try_index(id) {
558 if self.nodes.contains_key(&v) && marked.insert(v) {
559 stack.push(v);
560 }
561 }
562 }
563
564 fn attach_silent(&mut self, parent: NodeId, child: NodeId) {
567 self.node_mut(child).parent = Some(parent);
568 self.node_mut(parent).children.push(child);
569 }
570
571 fn copy_fragment_node(&mut self, src: &StaticDocument, sid: StaticNodeId) -> NodeId {
574 let new = match src.kind(sid) {
575 NodeKind::Element => {
576 let mut node = Node::new(NodeKind::Element);
577 node.name = src.element_name(sid).cloned();
578 for attr in src.attributes(sid) {
579 node.attrs.push((attr.name.clone(), attr.value.to_owned()));
580 }
581 self.push(node)
582 },
583 kind @ (NodeKind::Text | NodeKind::Comment) => {
584 let mut node = Node::new(kind);
585 node.text = src.text(sid).map(str::to_owned);
586 self.push(node)
587 },
588 other => self.push(Node::new(other)),
589 };
590 let children: Vec<StaticNodeId> = src.dom_children(sid).collect();
591 for child in children {
592 let copied = self.copy_fragment_node(src, child);
593 self.attach_silent(new, copied);
594 }
595 new
596 }
597
598 fn fragment_body(doc: &StaticDocument) -> Option<StaticNodeId> {
600 let html = doc.document_element()?;
601 doc.dom_children(html).find(|&c| {
602 doc.element_name(c)
603 .is_some_and(|q| q.local == LocalName::from("body"))
604 })
605 }
606}
607
608fn qual_name_bytes(name: &QualName) -> usize {
609 name.ns.as_ref().len()
610 + name.local.as_ref().len()
611 + name
612 .prefix
613 .as_ref()
614 .map_or(0, |prefix| prefix.as_ref().len())
615}
616
617impl LayoutDom for ScriptedDom {
618 type NodeId = NodeId;
619
620 fn document(&self) -> NodeId {
621 self.root
622 }
623
624 fn is_live(&self, id: NodeId) -> bool {
629 self.try_index(id)
630 .is_some_and(|v| self.nodes.contains_key(&v))
631 }
632
633 fn parent(&self, id: NodeId) -> Option<NodeId> {
634 self.node(id).parent
635 }
636
637 fn prev_sibling(&self, id: NodeId) -> Option<NodeId> {
638 self.sibling(id, -1)
639 }
640
641 fn next_sibling(&self, id: NodeId) -> Option<NodeId> {
642 self.sibling(id, 1)
643 }
644
645 fn dom_children(&self, id: NodeId) -> impl Iterator<Item = NodeId> + '_ {
646 self.node(id).children.iter().copied()
647 }
648
649 fn kind(&self, id: NodeId) -> NodeKind {
650 self.node(id).kind
651 }
652
653 fn opaque_id(&self, id: NodeId) -> u64 {
654 let _ = self.index(id);
658 id.0 as u64
659 }
660
661 fn element_name(&self, id: NodeId) -> Option<&QualName> {
662 self.node(id).name.as_ref()
663 }
664
665 fn attribute(&self, id: NodeId, ns: &Namespace, local: &LocalName) -> Option<&str> {
666 self.node(id)
667 .attrs
668 .iter()
669 .find(|(name, _)| &name.ns == ns && &name.local == local)
670 .map(|(_, value)| value.as_str())
671 }
672
673 fn attributes(&self, id: NodeId) -> impl Iterator<Item = AttributeView<'_>> + '_ {
674 self.node(id)
675 .attrs
676 .iter()
677 .map(|(name, value)| AttributeView {
678 name,
679 value: value.as_str(),
680 })
681 }
682
683 fn text(&self, id: NodeId) -> Option<&str> {
684 self.node(id).text.as_deref()
685 }
686}
687
688impl LayoutDomMut for ScriptedDom {
689 fn create_element(&mut self, name: QualName) -> NodeId {
690 let mut node = Node::new(NodeKind::Element);
691 node.name = Some(name);
692 self.push(node)
693 }
694
695 fn create_text(&mut self, data: &str) -> NodeId {
696 let mut node = Node::new(NodeKind::Text);
697 node.text = Some(data.to_owned());
698 self.push(node)
699 }
700
701 fn append_child(&mut self, parent: NodeId, child: NodeId) {
702 if let Some(former_parent) = self.node(child).parent {
706 self.mutations.push(DomMutation::Removed {
707 node: child,
708 former_parent,
709 });
710 }
711 self.detach(child);
712 self.node_mut(child).parent = Some(parent);
713 self.node_mut(parent).children.push(child);
714 self.mutations.push(DomMutation::Inserted {
715 node: child,
716 parent,
717 });
718 }
719
720 fn insert_before(&mut self, parent: NodeId, child: NodeId, reference: Option<NodeId>) {
721 if let Some(former_parent) = self.node(child).parent {
725 self.mutations.push(DomMutation::Removed {
726 node: child,
727 former_parent,
728 });
729 }
730 self.attach_at(parent, child, reference);
731 self.mutations.push(DomMutation::Inserted {
732 node: child,
733 parent,
734 });
735 }
736
737 fn move_before(&mut self, parent: NodeId, child: NodeId, reference: Option<NodeId>) {
738 let from_parent = self.node(child).parent;
739 let reference = if reference == Some(child) {
742 self.sibling(child, 1)
743 } else {
744 reference
745 };
746 if from_parent == Some(parent) && self.sibling(child, 1) == reference {
749 return;
750 }
751 self.attach_at(parent, child, reference);
752 match from_parent {
753 Some(from_parent) => self.mutations.push(DomMutation::Moved {
754 node: child,
755 from_parent,
756 to_parent: parent,
757 }),
758 None => self.mutations.push(DomMutation::Inserted {
762 node: child,
763 parent,
764 }),
765 }
766 }
767
768 fn remove(&mut self, node: NodeId) {
769 let former_parent = self.node(node).parent;
770 self.detach(node);
771 if let Some(former_parent) = former_parent {
772 self.mutations.push(DomMutation::Removed {
773 node,
774 former_parent,
775 });
776 }
777 self.drop_subtree(node);
778 }
779
780 fn set_attribute(&mut self, node: NodeId, name: QualName, value: &str) {
781 let attrs = &mut self.node_mut(node).attrs;
782 let old_value;
786 if let Some(existing) = attrs
787 .iter_mut()
788 .find(|(n, _)| n.ns == name.ns && n.local == name.local)
789 {
790 old_value = Some(std::mem::replace(&mut existing.1, value.to_owned()));
791 } else {
792 old_value = None;
793 attrs.push((name.clone(), value.to_owned()));
794 }
795 self.mutations.push(DomMutation::AttributeChanged {
796 node,
797 name,
798 old_value,
799 });
800 }
801
802 fn remove_attribute(&mut self, node: NodeId, name: QualName) {
803 let removed = {
807 let attrs = &mut self.node_mut(node).attrs;
808 attrs
809 .iter()
810 .position(|(n, _)| n.ns == name.ns && n.local == name.local)
811 .map(|pos| attrs.remove(pos).1)
812 };
813 if let Some(old) = removed {
814 self.mutations.push(DomMutation::AttributeChanged {
815 node,
816 name,
817 old_value: Some(old),
818 });
819 }
820 }
821
822 fn set_text(&mut self, node: NodeId, data: &str) {
823 self.node_mut(node).text = Some(data.to_owned());
824 self.mutations
825 .push(DomMutation::CharacterDataChanged { node });
826 }
827
828 fn set_inner_html(&mut self, node: NodeId, html: &str) {
829 let existing = std::mem::take(&mut self.node_mut(node).children);
831 for child in existing {
832 self.node_mut(child).parent = None;
833 self.drop_subtree(child);
834 }
835 let fragment = StaticDocument::parse(html);
839 if let Some(body) = Self::fragment_body(&fragment) {
840 let body_children: Vec<StaticNodeId> = fragment.dom_children(body).collect();
841 for child in body_children {
842 let copied = self.copy_fragment_node(&fragment, child);
843 self.attach_silent(node, copied);
844 }
845 }
846 self.mutations.push(DomMutation::SubtreeReplaced { node });
847 }
848
849 fn drain_mutations(&mut self, out: &mut Vec<DomMutation<NodeId>>) {
850 out.append(&mut self.mutations);
851 }
852}
853
854#[cfg(test)]
855mod tests {
856 use super::*;
857 use layout_dom_api::CapturedMutation;
858
859 fn qual(local: &str) -> QualName {
860 QualName::new(None, Namespace::from(""), LocalName::from(local))
861 }
862
863 #[test]
864 fn mutate_read_and_record() {
865 let mut dom = ScriptedDom::new();
866 let root = dom.document();
867
868 let div = dom.create_element(qual("div"));
869 dom.append_child(root, div);
870 let text = dom.create_text("hello");
871 dom.append_child(div, text);
872 dom.set_attribute(div, qual("id"), "main");
873
874 assert_eq!(dom.kind(div), NodeKind::Element);
876 assert_eq!(dom.element_name(div).unwrap().local, LocalName::from("div"));
877 assert_eq!(dom.dom_children(root).collect::<Vec<_>>(), vec![div]);
878 assert_eq!(dom.dom_children(div).collect::<Vec<_>>(), vec![text]);
879 assert_eq!(dom.parent(text), Some(div));
880 assert_eq!(dom.text(text), Some("hello"));
881 assert_eq!(
882 dom.attribute(div, &Namespace::from(""), &LocalName::from("id")),
883 Some("main")
884 );
885
886 let mut muts = Vec::new();
888 dom.drain_mutations(&mut muts);
889 assert_eq!(muts.len(), 3);
890 let mut again = Vec::new();
891 dom.drain_mutations(&mut again);
892 assert!(again.is_empty());
893 }
894
895 #[test]
896 fn set_inner_html_builds_subtree() {
897 let mut dom = ScriptedDom::new();
898 let root = dom.document();
899 let div = dom.create_element(qual("div"));
900 dom.append_child(root, div);
901 let mut drained = Vec::new();
902 dom.drain_mutations(&mut drained); dom.set_inner_html(div, "<p>hi</p><span>x</span>");
905
906 let kids: Vec<_> = dom.dom_children(div).collect();
907 assert_eq!(kids.len(), 2);
908 assert_eq!(
909 dom.element_name(kids[0]).unwrap().local,
910 LocalName::from("p")
911 );
912 assert_eq!(
913 dom.element_name(kids[1]).unwrap().local,
914 LocalName::from("span")
915 );
916 let p_kids: Vec<_> = dom.dom_children(kids[0]).collect();
918 assert_eq!(p_kids.len(), 1);
919 assert_eq!(dom.text(p_kids[0]), Some("hi"));
920
921 let mut muts = Vec::new();
922 dom.drain_mutations(&mut muts);
923 assert!(matches!(
924 muts.as_slice(),
925 [DomMutation::SubtreeReplaced { .. }]
926 ));
927 }
928
929 #[test]
930 fn siblings_and_remove() {
931 let mut dom = ScriptedDom::new();
932 let root = dom.document();
933 let a = dom.create_element(qual("a"));
934 let b = dom.create_element(qual("b"));
935 dom.append_child(root, a);
936 dom.append_child(root, b);
937
938 assert_eq!(dom.next_sibling(a), Some(b));
939 assert_eq!(dom.prev_sibling(b), Some(a));
940 assert_eq!(dom.prev_sibling(a), None);
941
942 let mut drained = Vec::new();
943 dom.drain_mutations(&mut drained); dom.remove(a);
946 assert_eq!(dom.dom_children(root).collect::<Vec<_>>(), vec![b]);
947 assert_eq!(dom.next_sibling(b), None);
948
949 let mut muts = Vec::new();
950 dom.drain_mutations(&mut muts);
951 assert!(matches!(muts.as_slice(), [DomMutation::Removed { .. }]));
952 }
953
954 #[test]
955 fn insert_before_orders_and_appends() {
956 let mut dom = ScriptedDom::new();
957 let root = dom.document();
958 let a = dom.create_element(qual("a"));
959 let c = dom.create_element(qual("c"));
960 dom.append_child(root, a);
961 dom.append_child(root, c);
962 let mut drained = Vec::new();
963 dom.drain_mutations(&mut drained); let b = dom.create_element(qual("b"));
967 dom.insert_before(root, b, Some(c));
968 assert_eq!(dom.dom_children(root).collect::<Vec<_>>(), vec![a, b, c]);
969 assert_eq!(dom.parent(b), Some(root));
970
971 let d = dom.create_element(qual("d"));
973 dom.insert_before(root, d, None);
974 assert_eq!(dom.dom_children(root).collect::<Vec<_>>(), vec![a, b, c, d]);
975
976 let orphan = dom.create_element(qual("orphan"));
978 let e = dom.create_element(qual("e"));
979 dom.insert_before(root, e, Some(orphan));
980 assert_eq!(
981 dom.dom_children(root).collect::<Vec<_>>(),
982 vec![a, b, c, d, e]
983 );
984
985 let mut muts = Vec::new();
988 dom.drain_mutations(&mut muts);
989 assert_eq!(muts.len(), 3);
990 assert!(
991 muts.iter()
992 .all(|m| matches!(m, DomMutation::Inserted { parent, .. } if *parent == root))
993 );
994 }
995
996 #[test]
997 fn in_tree_insert_reports_the_removal_too() {
998 let mut dom = ScriptedDom::new();
1002 let root = dom.document();
1003 let a = dom.create_element(qual("a"));
1004 let b = dom.create_element(qual("b"));
1005 let child = dom.create_element(qual("child"));
1006 dom.append_child(root, a);
1007 dom.append_child(root, b);
1008 dom.append_child(a, child);
1009 let mut drained = Vec::new();
1010 dom.drain_mutations(&mut drained);
1011
1012 dom.insert_before(b, child, None);
1013 let mut muts = Vec::new();
1014 dom.drain_mutations(&mut muts);
1015 assert_eq!(
1016 muts,
1017 vec![
1018 DomMutation::Removed {
1019 node: child,
1020 former_parent: a,
1021 },
1022 DomMutation::Inserted {
1023 node: child,
1024 parent: b,
1025 },
1026 ]
1027 );
1028 }
1029
1030 #[test]
1031 fn move_before_moves_across_parents_with_one_moved_record() {
1032 let mut dom = ScriptedDom::new();
1033 let root = dom.document();
1034 let a = dom.create_element(qual("a"));
1035 let b = dom.create_element(qual("b"));
1036 let child = dom.create_element(qual("child"));
1037 let b_kid = dom.create_element(qual("bkid"));
1038 dom.append_child(root, a);
1039 dom.append_child(root, b);
1040 dom.append_child(a, child);
1041 dom.append_child(b, b_kid);
1042 let mut drained = Vec::new();
1043 dom.drain_mutations(&mut drained);
1044
1045 dom.move_before(b, child, Some(b_kid));
1046 assert_eq!(dom.parent(child), Some(b));
1047 assert_eq!(dom.dom_children(b).collect::<Vec<_>>(), vec![child, b_kid]);
1048 assert!(dom.dom_children(a).next().is_none());
1049
1050 let mut muts = Vec::new();
1053 dom.drain_mutations(&mut muts);
1054 assert_eq!(
1055 muts,
1056 vec![DomMutation::Moved {
1057 node: child,
1058 from_parent: a,
1059 to_parent: b,
1060 }]
1061 );
1062 }
1063
1064 #[test]
1065 fn move_before_same_parent_reorders_and_noops_in_place() {
1066 let mut dom = ScriptedDom::new();
1067 let root = dom.document();
1068 let a = dom.create_element(qual("a"));
1069 let b = dom.create_element(qual("b"));
1070 let c = dom.create_element(qual("c"));
1071 dom.append_child(root, a);
1072 dom.append_child(root, b);
1073 dom.append_child(root, c);
1074 let mut drained = Vec::new();
1075 dom.drain_mutations(&mut drained);
1076
1077 dom.move_before(root, c, Some(a));
1079 assert_eq!(dom.dom_children(root).collect::<Vec<_>>(), vec![c, a, b]);
1080 let mut muts = Vec::new();
1081 dom.drain_mutations(&mut muts);
1082 assert_eq!(
1083 muts,
1084 vec![DomMutation::Moved {
1085 node: c,
1086 from_parent: root,
1087 to_parent: root,
1088 }]
1089 );
1090
1091 dom.move_before(root, c, Some(a));
1095 dom.move_before(root, c, Some(c));
1096 dom.move_before(root, b, None);
1097 let mut noop = Vec::new();
1098 dom.drain_mutations(&mut noop);
1099 assert_eq!(noop, Vec::new());
1100 assert_eq!(dom.dom_children(root).collect::<Vec<_>>(), vec![c, a, b]);
1101 }
1102
1103 #[test]
1104 fn remove_attribute_records_and_noops() {
1105 let mut dom = ScriptedDom::new();
1106 let root = dom.document();
1107 let div = dom.create_element(qual("div"));
1108 dom.append_child(root, div);
1109 dom.set_attribute(div, qual("id"), "main");
1110 let mut drained = Vec::new();
1111 dom.drain_mutations(&mut drained); dom.remove_attribute(div, qual("id"));
1115 assert_eq!(
1116 dom.attribute(div, &Namespace::from(""), &LocalName::from("id")),
1117 None
1118 );
1119 let mut muts = Vec::new();
1120 dom.drain_mutations(&mut muts);
1121 assert!(matches!(
1122 muts.as_slice(),
1123 [DomMutation::AttributeChanged { old_value: Some(v), .. }] if v.as_str() == "main"
1124 ));
1125
1126 dom.remove_attribute(div, qual("id"));
1128 let mut again = Vec::new();
1129 dom.drain_mutations(&mut again);
1130 assert!(again.is_empty());
1131 }
1132
1133 #[test]
1134 fn drained_mutations_round_trip_through_captured_postcard() {
1135 let mut dom = ScriptedDom::new();
1136 let root = dom.document();
1137 let div = dom.create_element(qual("div"));
1138 dom.append_child(root, div);
1139 let mut drained = Vec::new();
1140 dom.drain_mutations(&mut drained); dom.set_attribute(div, qual("id"), "main");
1143 dom.remove_attribute(div, qual("id"));
1144
1145 let mut muts = Vec::new();
1146 dom.drain_mutations(&mut muts);
1147 let captured: Vec<_> = muts
1148 .iter()
1149 .map(|m| CapturedMutation::capture(m, |id| dom.capture_node_id(*id)))
1150 .collect();
1151 let bytes = postcard::to_stdvec(&captured).expect("serialize captured mutations");
1152 let decoded: Vec<CapturedMutation> =
1153 postcard::from_bytes(&bytes).expect("decode captured mutations");
1154 let replayed: Vec<_> = decoded
1155 .into_iter()
1156 .map(|m| m.replay(|raw| dom.remint_node_id(raw)))
1157 .collect();
1158
1159 assert_eq!(replayed, muts);
1160 }
1161
1162 #[test]
1163 fn stats_count_node_kinds_attributes_and_bytes() {
1164 let mut dom = ScriptedDom::new();
1165 let root = dom.document();
1166 let html = dom.create_element(qual("html"));
1167 let body = dom.create_element(qual("body"));
1168 let text = dom.create_text("hello");
1169 let comment = dom.create_comment("note");
1170 let frag = dom.create_fragment();
1171 dom.append_child(root, html);
1172 dom.append_child(html, body);
1173 dom.append_child(body, text);
1174 dom.append_child(body, comment);
1175 dom.append_child(body, frag);
1176 dom.set_attribute(body, qual("class"), "main");
1177 dom.set_attribute(body, qual("data-x"), "1");
1178
1179 let stats = dom.stats();
1180 assert_eq!(stats.live_nodes, 6);
1181 assert_eq!(stats.node_kinds.documents, 1);
1182 assert_eq!(stats.node_kinds.elements, 2);
1183 assert_eq!(stats.node_kinds.text, 1);
1184 assert_eq!(stats.node_kinds.comments, 1);
1185 assert_eq!(stats.node_kinds.document_fragments, 1);
1186 assert_eq!(stats.attribute_count, 2);
1187 assert!(stats.estimated_bytes >= std::mem::size_of::<ScriptedDom>());
1188 }
1189
1190 #[test]
1193 fn secondary_root_is_same_document() {
1194 let mut dom = ScriptedDom::new();
1197 let primary = dom.document();
1198 let secondary = dom.create_document();
1199 let div = dom.create_element(qual("div"));
1200 dom.append_child(secondary, div);
1201 assert_eq!(dom.parent(div), Some(secondary));
1202 assert_ne!(primary, secondary);
1203 assert_eq!(dom.kind(primary), NodeKind::Document);
1205 assert_eq!(dom.kind(secondary), NodeKind::Document);
1206 }
1207
1208 #[cfg(all(debug_assertions, target_pointer_width = "64"))]
1212 #[test]
1213 #[should_panic(expected = "different document")]
1214 fn cross_document_node_id_panics() {
1215 let mut a = ScriptedDom::new();
1216 let b = ScriptedDom::new();
1217 let id_in_a = a.create_element(qual("div"));
1218 let _ = b.kind(id_in_a);
1220 }
1221
1222 #[cfg(all(debug_assertions, target_pointer_width = "64"))]
1223 #[test]
1224 fn distinct_documents_get_distinct_tags() {
1225 let a = ScriptedDom::new();
1226 let b = ScriptedDom::new();
1227 assert_ne!(a.document().raw(), b.document().raw());
1229 }
1230
1231 #[test]
1232 fn captured_node_id_remints_for_another_document() {
1233 let a = ScriptedDom::new();
1234 let b = ScriptedDom::new();
1235 let captured = a.capture_node_id(a.document());
1236 let reminted = b.remint_node_id(captured);
1237 assert!(b.is_live(reminted));
1238 #[cfg(all(debug_assertions, target_pointer_width = "64"))]
1239 {
1240 assert!(!b.is_live(a.document()));
1241 assert_ne!(a.document(), reminted);
1242 }
1243 }
1244
1245 #[test]
1251 fn dangle_contract_churn_across_frames() {
1252 let mut dom = ScriptedDom::new();
1253 let root = dom.document();
1254
1255 let a = dom.create_element(qual("a"));
1257 let b = dom.create_element(qual("b"));
1258 dom.append_child(root, a);
1259 dom.append_child(root, b);
1260 let mut drained = Vec::new();
1261 dom.drain_mutations(&mut drained);
1262
1263 assert!(dom.is_live(root));
1265 assert!(dom.is_live(a));
1266 assert!(dom.is_live(b));
1267
1268 dom.remove(a);
1271 dom.drain_mutations(&mut drained);
1272 assert!(!dom.is_live(a));
1273 assert!(dom.is_live(b));
1274 assert_eq!(dom.dom_children(root).collect::<Vec<_>>(), vec![b]);
1275
1276 dom.remove_child(b);
1279 dom.drain_mutations(&mut drained);
1280 assert!(dom.is_live(b), "an orphaned node stays live until dropped");
1281 assert!(dom.dom_children(root).collect::<Vec<_>>().is_empty());
1282 dom.append_child(root, b); assert_eq!(dom.dom_children(root).collect::<Vec<_>>(), vec![b]);
1284 assert!(dom.is_live(b));
1285 }
1286
1287 #[test]
1288 fn is_live_is_false_for_dropped_and_foreign_ids() {
1289 let mut dom = ScriptedDom::new();
1290 let root = dom.document();
1291 let n = dom.create_element(qual("n"));
1292 dom.append_child(root, n);
1293 assert!(dom.is_live(n));
1294 dom.remove(n);
1295 assert!(!dom.is_live(n));
1296
1297 let other = ScriptedDom::new();
1300 let foreign = {
1301 let mut o = other;
1302 o.create_element(qual("x"))
1303 };
1304 assert!(!dom.is_live(foreign));
1305 }
1306
1307 const NO_PINS: [NodeId; 0] = [];
1310
1311 #[test]
1314 fn collect_reaps_unpinned_orphans_keeps_pinned_components() {
1315 let mut dom = ScriptedDom::new();
1316 let root = dom.document();
1317 let attached = dom.create_element(qual("attached"));
1318 dom.append_child(root, attached);
1319
1320 let parent = dom.create_element(qual("p"));
1322 let mid = dom.create_element(qual("mid"));
1323 let leaf = dom.create_element(qual("leaf"));
1324 dom.append_child(parent, mid);
1325 dom.append_child(mid, leaf);
1326
1327 let live_before = dom.live_node_count();
1330 let pruned = dom.collect(NO_PINS);
1331 assert_eq!(pruned, 3, "the 3-node orphan subtree is reaped");
1332 assert_eq!(dom.live_node_count(), live_before - 3);
1333 assert!(dom.is_live(root) && dom.is_live(attached));
1334 assert!(!dom.is_live(parent) && !dom.is_live(mid) && !dom.is_live(leaf));
1335
1336 let parent = dom.create_element(qual("p"));
1339 let mid = dom.create_element(qual("mid"));
1340 let leaf = dom.create_element(qual("leaf"));
1341 dom.append_child(parent, mid);
1342 dom.append_child(mid, leaf);
1343 let pruned = dom.collect([leaf]); assert_eq!(pruned, 0, "a pin on the leaf spares the whole component");
1345 assert!(dom.is_live(parent) && dom.is_live(mid) && dom.is_live(leaf));
1346
1347 assert_eq!(dom.collect(NO_PINS), 3);
1349 assert!(!dom.is_live(parent) && !dom.is_live(mid) && !dom.is_live(leaf));
1350 }
1351
1352 #[test]
1355 fn collect_bounds_memory_under_churn() {
1356 let mut dom = ScriptedDom::new();
1357 let root = dom.document();
1358 let baseline = dom.live_node_count();
1359
1360 let mut peak = baseline;
1361 for _ in 0..500 {
1362 let host = dom.create_element(qual("host"));
1365 dom.append_child(root, host);
1366 for _ in 0..8 {
1367 let kid = dom.create_element(qual("kid"));
1368 dom.append_child(host, kid);
1369 }
1370 peak = peak.max(dom.live_node_count());
1371 dom.remove_child(host); dom.collect(NO_PINS); }
1374
1375 assert!(dom.next_id > 4000, "ids are monotonic (no reuse)");
1377 assert_eq!(dom.live_node_count(), baseline, "store plateaus, not grows");
1378 assert!(
1379 peak < baseline + 32,
1380 "peak stays tiny — only one subtree at a time"
1381 );
1382 }
1383
1384 #[test]
1387 fn idle_collect_reaps_orphans_without_mutations() {
1388 let mut dom = ScriptedDom::new();
1389 let root = dom.document();
1390 let o = dom.create_element(qual("o"));
1391 dom.append_child(root, o);
1392 dom.remove_child(o); let mut drained = Vec::new();
1394 dom.drain_mutations(&mut drained);
1395
1396 assert!(dom.is_live(o));
1398 assert_eq!(dom.collect(NO_PINS), 1);
1399 assert!(!dom.is_live(o));
1400 }
1401
1402 #[test]
1406 fn secondary_document_is_pin_kept() {
1407 let mut dom = ScriptedDom::new();
1408 let secondary = dom.create_document();
1409 let body = dom.create_element(qual("body"));
1410 dom.append_child(secondary, body);
1411
1412 assert_eq!(dom.collect([secondary]), 0);
1414 assert!(dom.is_live(secondary) && dom.is_live(body));
1415
1416 assert_eq!(dom.collect(NO_PINS), 2);
1418 assert!(!dom.is_live(secondary) && !dom.is_live(body));
1419 }
1420
1421 #[test]
1422 fn pins_pin_unpin_and_retire() {
1423 let id = NodeId::from_raw;
1424 let mut pins = Pins::new();
1425 assert!(pins.is_empty());
1426
1427 pins.pin(id(0x10));
1428 pins.pin(id(0x20));
1429 pins.pin(id(0x10)); assert_eq!(pins.len(), 2);
1431 assert!(pins.is_pinned(id(0x10)));
1432
1433 assert!(pins.unpin(id(0x20)));
1434 assert!(!pins.unpin(id(0xAA))); pins.pin(id(0x30));
1437 assert_eq!(pins.retire_dead([id(0x10), id(0x30), id(0xBB)]), 2);
1438 assert!(pins.is_empty());
1439 }
1440
1441 #[test]
1442 fn pins_clear() {
1443 let mut pins = Pins::new();
1444 pins.pin(NodeId::from_raw(1));
1445 pins.pin(NodeId::from_raw(2));
1446 assert_eq!(pins.len(), 2);
1447 pins.clear();
1448 assert!(pins.is_empty());
1449 }
1450}