orinium_browser/engine/layouter/
dom_snapshot.rs1use std::cell::RefCell;
10use std::collections::HashMap;
11use std::rc::{Rc, Weak};
12
13use crate::engine::html::HtmlNodeType;
14use crate::engine::html::parser::DomTree;
15use crate::engine::tree::{NodeRef, TreeNode};
16
17pub type NodeId = u32;
19
20#[derive(Debug, Clone)]
22pub struct SnapNode {
23 pub kind: HtmlNodeType,
24 pub children: Vec<NodeId>,
25 pub dom_id: u64,
29}
30
31#[derive(Debug, Default)]
36pub struct DomSnapshot {
37 nodes: Vec<SnapNode>,
38 roots: Vec<NodeId>,
39}
40
41impl DomSnapshot {
42 pub fn from_tree(
48 root: &NodeRef<HtmlNodeType>,
49 ) -> (Self, Vec<Weak<RefCell<TreeNode<HtmlNodeType>>>>) {
50 let mut snapshot = DomSnapshot::default();
51 let mut dom_refs: Vec<Weak<RefCell<TreeNode<HtmlNodeType>>>> = Vec::new();
52 let id = snapshot.walk(root, &mut dom_refs);
53 snapshot.roots.push(id);
54 (snapshot, dom_refs)
55 }
56
57 pub fn from_mirror(root: &NodeRef<HtmlNodeType>, dom_ids: &HashMap<usize, u64>) -> Self {
64 fn walk(
65 snapshot: &mut DomSnapshot,
66 node: &NodeRef<HtmlNodeType>,
67 dom_ids: &HashMap<usize, u64>,
68 ) -> NodeId {
69 let id = snapshot.nodes.len() as NodeId;
70 snapshot.nodes.push(SnapNode {
71 kind: node.borrow().value.clone(),
72 children: Vec::new(),
73 dom_id: dom_ids
74 .get(&(Rc::as_ptr(node) as usize))
75 .copied()
76 .unwrap_or(0),
77 });
78 let children: Vec<NodeId> = node
79 .borrow()
80 .children()
81 .iter()
82 .map(|child| walk(snapshot, child, dom_ids))
83 .collect();
84 snapshot.nodes[id as usize].children = children;
85 id
86 }
87 let mut snapshot = DomSnapshot::default();
88 let id = walk(&mut snapshot, root, dom_ids);
89 snapshot.roots.push(id);
90 snapshot
91 }
92
93 pub fn into_tree(&self) -> (DomTree, HashMap<usize, u64>) {
99 fn build(id: NodeId, snapshot: &DomSnapshot) -> NodeRef<HtmlNodeType> {
100 let node = TreeNode::new(snapshot.nodes[id as usize].kind.clone());
101 for &child in &snapshot.nodes[id as usize].children {
102 let child_node = build(child, snapshot);
103 TreeNode::add_child(&node, child_node);
104 }
105 node
106 }
107
108 let root = build(self.roots[0], self);
109 let tree = DomTree::from_root(root);
110 let mut dom_ids = HashMap::new();
111 let mut index = 0usize;
112 tree.traverse(|node| {
113 let dom_id = self.nodes[index].dom_id;
114 if dom_id != 0 {
115 dom_ids.insert(Rc::as_ptr(node) as usize, dom_id);
116 }
117 index += 1;
118 });
119 (tree, dom_ids)
120 }
121
122 fn walk(
123 &mut self,
124 node: &NodeRef<HtmlNodeType>,
125 dom_refs: &mut Vec<Weak<RefCell<TreeNode<HtmlNodeType>>>>,
126 ) -> NodeId {
127 let id = self.nodes.len() as NodeId;
128 self.nodes.push(SnapNode {
129 kind: node.borrow().value.clone(),
130 children: Vec::new(),
131 dom_id: 0,
132 });
133 dom_refs.push(Rc::downgrade(node));
134 let children: Vec<NodeId> = node
135 .borrow()
136 .children()
137 .iter()
138 .map(|child| self.walk(child, dom_refs))
139 .collect();
140 self.nodes[id as usize].children = children;
141 id
142 }
143
144 pub fn roots(&self) -> &[NodeId] {
146 &self.roots
147 }
148
149 pub fn nodes(&self) -> &[SnapNode] {
151 &self.nodes
152 }
153
154 pub fn node(&self, id: NodeId) -> &SnapNode {
156 &self.nodes[id as usize]
157 }
158
159 pub fn children(&self, id: NodeId) -> &[NodeId] {
161 &self.nodes[id as usize].children
162 }
163
164 pub fn inner_text(&self, id: NodeId) -> String {
166 let node = &self.nodes[id as usize];
167 match &node.kind {
168 HtmlNodeType::Text(content) => content.clone(),
169 HtmlNodeType::Element { .. } | HtmlNodeType::Document => node
170 .children
171 .iter()
172 .map(|&child| self.inner_text(child))
173 .collect(),
174 _ => String::new(),
175 }
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use crate::engine::html::parser::DomTree;
183 use crate::engine::html::parser::Parser as HtmlParser;
184
185 fn tree(html: &str) -> DomTree {
186 HtmlParser::new(html).parse()
187 }
188
189 #[test]
190 fn snapshot_is_built_in_preorder_and_is_send() {
191 let dom = tree("<html><body><div><p>hi</p></div><button>ok</button></body></html>");
192 let (snapshot, _dom_refs) = DomSnapshot::from_tree(&dom.root);
193 let root = snapshot.roots()[0];
194
195 let doc = snapshot.node(root);
197 assert_eq!(doc.kind.tag_name(), None);
198
199 let html = doc.children[0];
201 let html_node = snapshot.node(html);
202 assert_eq!(html_node.kind.tag_name(), Some("html"));
203 assert_eq!(html_node.children.len(), 1);
204
205 let body = html_node.children[0];
207 assert_eq!(snapshot.node(body).kind.tag_name(), Some("body"));
208
209 std::thread::spawn(move || {
211 let _ = snapshot.inner_text(root);
212 })
213 .join()
214 .unwrap();
215 }
216
217 #[test]
218 fn inner_text_concatenates_descendants() {
219 let dom = tree("<div><p>hello</p><p>world</p></div>");
220 let (snapshot, _) = DomSnapshot::from_tree(&dom.root);
221 let root = snapshot.roots()[0];
222 assert_eq!(snapshot.inner_text(root), "helloworld");
223 }
224
225 #[test]
226 fn dom_refs_map_node_id_to_live_node() {
227 let dom = tree("<input value='a'>");
228 let (snapshot, dom_refs) = DomSnapshot::from_tree(&dom.root);
229 let root = snapshot.roots()[0];
230
231 fn find(snapshot: &DomSnapshot, id: NodeId, tag: &str) -> Option<NodeId> {
233 if snapshot.node(id).kind.tag_name() == Some(tag) {
234 return Some(id);
235 }
236 snapshot
237 .children(id)
238 .iter()
239 .find_map(|&c| find(snapshot, c, tag))
240 }
241 let input_id = find(&snapshot, root, "input").unwrap();
242
243 let live = dom_refs[input_id as usize].upgrade().unwrap();
244 assert_eq!(live.borrow().value.tag_name(), Some("input"));
245 }
246
247 #[test]
248 fn from_tree_roundtrip_preserves_structure() {
249 let dom = tree("<html><body><div><p>hi</p></div><button>ok</button></body></html>");
250 let (snapshot, _) = DomSnapshot::from_tree(&dom.root);
251 let (rebuilt, dom_ids) = snapshot.into_tree();
252
253 assert_eq!(rebuilt.root.borrow().value.tag_name(), None);
254 assert!(dom_ids.is_empty());
255 assert_eq!(
256 rebuilt.root.borrow().children()[0]
257 .borrow()
258 .value
259 .tag_name(),
260 Some("html")
261 );
262 assert_eq!(
263 rebuilt.root.borrow().children()[0].borrow().children()[0]
264 .borrow()
265 .value
266 .tag_name(),
267 Some("body")
268 );
269 assert_eq!(rebuilt.version(), 0);
270 }
271
272 #[test]
273 fn from_mirror_and_into_tree_preserve_dom_ids() {
274 let dom = tree("<div><p>hi</p><p>yo</p></div>");
275
276 let mut dom_ids = HashMap::new();
278 let mut next = 1u64;
279 let mut counter = 0usize;
280 dom.traverse(|node| {
281 if counter.is_multiple_of(2) {
282 dom_ids.insert(Rc::as_ptr(node) as usize, next);
283 next += 1;
284 }
285 counter += 1;
286 });
287
288 let snapshot = DomSnapshot::from_mirror(&dom.root, &dom_ids);
289 let (rebuilt, rebuilt_ids) = snapshot.into_tree();
290
291 assert_eq!(rebuilt_ids.len(), dom_ids.len());
292 let mut rebuilt_preorder: Vec<u64> = Vec::new();
294 rebuilt.traverse(|node| {
295 rebuilt_preorder.push(
296 rebuilt_ids
297 .get(&(Rc::as_ptr(node) as usize))
298 .copied()
299 .unwrap_or(0),
300 );
301 });
302 let mut expected: Vec<u64> = Vec::new();
303 dom.traverse(|node| {
304 expected.push(
305 dom_ids
306 .get(&(Rc::as_ptr(node) as usize))
307 .copied()
308 .unwrap_or(0),
309 );
310 });
311 assert_eq!(rebuilt_preorder, expected);
312 }
313}