Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824//! DOM tree, nodes, and events.//!//! Arena-based DOM tree with Document, Element, Text, and Comment node types.//! Each node is stored in a flat `Vec` and referenced by `NodeId`.
use std::fmt;
/// A handle to a node in the DOM tree.#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]pub struct NodeId(usize);
impl NodeId { /// Returns the underlying index. pub fn index(self) -> usize { self.0 }}
/// An HTML/XML attribute (name-value pair).#[derive(Debug, Clone, PartialEq, Eq)]pub struct Attribute { pub name: String, pub value: String,}
/// The data specific to each node type.#[derive(Debug, Clone, PartialEq, Eq)]pub enum NodeData { /// The root document node. Document, /// An element node with tag name, attributes, and optional namespace. Element { tag_name: String, attributes: Vec<Attribute>, namespace: Option<String>, }, /// A text node containing character data. Text { data: String }, /// A comment node. Comment { data: String },}
/// A node in the DOM tree, with links to parent, children, and siblings.#[derive(Debug)]struct Node { data: NodeData, parent: Option<NodeId>, first_child: Option<NodeId>, last_child: Option<NodeId>, next_sibling: Option<NodeId>, prev_sibling: Option<NodeId>,}
/// The DOM document: an arena of nodes with a root document node.pub struct Document { nodes: Vec<Node>, root: NodeId,}
impl fmt::Debug for Document { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Document") .field("node_count", &self.nodes.len()) .field("root", &self.root) .finish() }}
impl Document { /// Create a new document with a root Document node. pub fn new() -> Self { let root_node = Node { data: NodeData::Document, parent: None, first_child: None, last_child: None, next_sibling: None, prev_sibling: None, }; Document { nodes: vec![root_node], root: NodeId(0), } }
/// Returns the root document node ID. pub fn root(&self) -> NodeId { self.root }
/// Create an element node. Returns its `NodeId` (not yet attached to the tree). pub fn create_element(&mut self, tag_name: &str) -> NodeId { self.create_element_ns(tag_name, None) }
/// Create an element node with an optional namespace. pub fn create_element_ns(&mut self, tag_name: &str, namespace: Option<&str>) -> NodeId { self.push_node(NodeData::Element { tag_name: tag_name.to_string(), attributes: Vec::new(), namespace: namespace.map(|s| s.to_string()), }) }
/// Create a text node. Returns its `NodeId` (not yet attached to the tree). pub fn create_text(&mut self, data: &str) -> NodeId { self.push_node(NodeData::Text { data: data.to_string(), }) }
/// Create a comment node. Returns its `NodeId` (not yet attached to the tree). pub fn create_comment(&mut self, data: &str) -> NodeId { self.push_node(NodeData::Comment { data: data.to_string(), }) }
/// Append `child` as the last child of `parent`. /// /// If `child` is already attached elsewhere, it is first removed from its /// current position. pub fn append_child(&mut self, parent: NodeId, child: NodeId) { self.detach(child);
let old_last = self.nodes[parent.0].last_child;
self.nodes[child.0].parent = Some(parent); self.nodes[child.0].prev_sibling = old_last; self.nodes[child.0].next_sibling = None;
if let Some(old_last_id) = old_last { self.nodes[old_last_id.0].next_sibling = Some(child); } else { self.nodes[parent.0].first_child = Some(child); }
self.nodes[parent.0].last_child = Some(child); }
/// Insert `new_child` before `reference` under `parent`. /// /// If `new_child` is already attached elsewhere, it is first removed. /// Panics if `reference` is not a child of `parent`. pub fn insert_before(&mut self, parent: NodeId, new_child: NodeId, reference: NodeId) { assert_eq!( self.nodes[reference.0].parent, Some(parent), "reference node is not a child of parent" );
self.detach(new_child);
let prev = self.nodes[reference.0].prev_sibling;
self.nodes[new_child.0].parent = Some(parent); self.nodes[new_child.0].next_sibling = Some(reference); self.nodes[new_child.0].prev_sibling = prev;
self.nodes[reference.0].prev_sibling = Some(new_child);
if let Some(prev_id) = prev { self.nodes[prev_id.0].next_sibling = Some(new_child); } else { self.nodes[parent.0].first_child = Some(new_child); } }
/// Remove `child` from `parent`. /// /// Panics if `child` is not a child of `parent`. pub fn remove_child(&mut self, parent: NodeId, child: NodeId) { assert_eq!( self.nodes[child.0].parent, Some(parent), "node is not a child of parent" ); self.detach(child); }
/// Returns the parent of `node`, or `None` for the root. pub fn parent(&self, node: NodeId) -> Option<NodeId> { self.nodes[node.0].parent }
/// Returns an iterator over the direct children of `node`. pub fn children(&self, node: NodeId) -> Children<'_> { Children { doc: self, next: self.nodes[node.0].first_child, } }
/// Returns the first child of `node`, if any. pub fn first_child(&self, node: NodeId) -> Option<NodeId> { self.nodes[node.0].first_child }
/// Returns the last child of `node`, if any. pub fn last_child(&self, node: NodeId) -> Option<NodeId> { self.nodes[node.0].last_child }
/// Returns the next sibling of `node`, if any. pub fn next_sibling(&self, node: NodeId) -> Option<NodeId> { self.nodes[node.0].next_sibling }
/// Returns the previous sibling of `node`, if any. pub fn prev_sibling(&self, node: NodeId) -> Option<NodeId> { self.nodes[node.0].prev_sibling }
/// Returns a reference to the node's data. pub fn node_data(&self, node: NodeId) -> &NodeData { &self.nodes[node.0].data }
/// Returns the tag name if `node` is an Element, or `None`. pub fn tag_name(&self, node: NodeId) -> Option<&str> { match &self.nodes[node.0].data { NodeData::Element { tag_name, .. } => Some(tag_name), _ => None, } }
/// Get an attribute value by name. Returns `None` if the node is not /// an element or the attribute is not present. pub fn get_attribute(&self, node: NodeId, name: &str) -> Option<&str> { match &self.nodes[node.0].data { NodeData::Element { attributes, .. } => attributes .iter() .find(|a| a.name == name) .map(|a| a.value.as_str()), _ => None, } }
/// Set an attribute on an element node. If the attribute already exists, /// its value is replaced. Does nothing if `node` is not an element. pub fn set_attribute(&mut self, node: NodeId, name: &str, value: &str) { if let NodeData::Element { attributes, .. } = &mut self.nodes[node.0].data { if let Some(attr) = attributes.iter_mut().find(|a| a.name == name) { attr.value = value.to_string(); } else { attributes.push(Attribute { name: name.to_string(), value: value.to_string(), }); } } }
/// Remove an attribute from an element node. Returns `true` if the /// attribute was present. pub fn remove_attribute(&mut self, node: NodeId, name: &str) -> bool { if let NodeData::Element { attributes, .. } = &mut self.nodes[node.0].data { let len_before = attributes.len(); attributes.retain(|a| a.name != name); attributes.len() < len_before } else { false } }
/// Returns the text content of a Text or Comment node, or `None` for /// other node types. pub fn text_content(&self, node: NodeId) -> Option<&str> { match &self.nodes[node.0].data { NodeData::Text { data } | NodeData::Comment { data } => Some(data), _ => None, } }
/// Set the text content of a Text or Comment node. /// Does nothing for other node types. pub fn set_text_content(&mut self, node: NodeId, new_data: &str) { match &mut self.nodes[node.0].data { NodeData::Text { data } | NodeData::Comment { data } => { *data = new_data.to_string(); } _ => {} } }
/// Returns the total number of nodes in the arena (including detached nodes). pub fn len(&self) -> usize { self.nodes.len() }
/// Returns true if the document has no nodes besides the root. pub fn is_empty(&self) -> bool { self.nodes.len() == 1 }
// --- private helpers ---
fn push_node(&mut self, data: NodeData) -> NodeId { let id = NodeId(self.nodes.len()); self.nodes.push(Node { data, parent: None, first_child: None, last_child: None, next_sibling: None, prev_sibling: None, }); id }
/// Detach a node from its current parent (if any), updating sibling links. fn detach(&mut self, node: NodeId) { let parent = match self.nodes[node.0].parent { Some(p) => p, None => return, };
let prev = self.nodes[node.0].prev_sibling; let next = self.nodes[node.0].next_sibling;
if let Some(prev_id) = prev { self.nodes[prev_id.0].next_sibling = next; } else { self.nodes[parent.0].first_child = next; }
if let Some(next_id) = next { self.nodes[next_id.0].prev_sibling = prev; } else { self.nodes[parent.0].last_child = prev; }
self.nodes[node.0].parent = None; self.nodes[node.0].prev_sibling = None; self.nodes[node.0].next_sibling = None; }}
impl Default for Document { fn default() -> Self { Self::new() }}
/// Iterator over the direct children of a node.pub struct Children<'a> { doc: &'a Document, next: Option<NodeId>,}
impl<'a> Iterator for Children<'a> { type Item = NodeId;
fn next(&mut self) -> Option<NodeId> { let current = self.next?; self.next = self.doc.nodes[current.0].next_sibling; Some(current) }}
#[cfg(test)]mod tests { use super::*;
#[test] fn new_document_has_root() { let doc = Document::new(); assert_eq!(doc.root(), NodeId(0)); assert_eq!(*doc.node_data(doc.root()), NodeData::Document); assert!(doc.children(doc.root()).next().is_none()); }
#[test] fn create_element() { let mut doc = Document::new(); let div = doc.create_element("div"); assert_eq!(doc.tag_name(div), Some("div")); assert!(doc.parent(div).is_none()); }
#[test] fn create_element_with_namespace() { let mut doc = Document::new(); let svg = doc.create_element_ns("svg", Some("http://www.w3.org/2000/svg")); match doc.node_data(svg) { NodeData::Element { namespace, .. } => { assert_eq!(namespace.as_deref(), Some("http://www.w3.org/2000/svg")); } _ => panic!("expected element"), } }
#[test] fn create_text() { let mut doc = Document::new(); let t = doc.create_text("hello"); assert_eq!(doc.text_content(t), Some("hello")); assert_eq!(doc.tag_name(t), None); }
#[test] fn create_comment() { let mut doc = Document::new(); let c = doc.create_comment("a comment"); assert_eq!(doc.text_content(c), Some("a comment")); match doc.node_data(c) { NodeData::Comment { data } => assert_eq!(data, "a comment"), _ => panic!("expected comment"), } }
#[test] fn append_child_single() { let mut doc = Document::new(); let root = doc.root(); let child = doc.create_element("div"); doc.append_child(root, child);
assert_eq!(doc.parent(child), Some(root)); assert_eq!(doc.first_child(root), Some(child)); assert_eq!(doc.last_child(root), Some(child)); assert!(doc.next_sibling(child).is_none()); assert!(doc.prev_sibling(child).is_none()); }
#[test] fn append_child_multiple() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); doc.append_child(root, b); doc.append_child(root, c);
assert_eq!(doc.first_child(root), Some(a)); assert_eq!(doc.last_child(root), Some(c));
assert_eq!(doc.next_sibling(a), Some(b)); assert_eq!(doc.next_sibling(b), Some(c)); assert!(doc.next_sibling(c).is_none());
assert!(doc.prev_sibling(a).is_none()); assert_eq!(doc.prev_sibling(b), Some(a)); assert_eq!(doc.prev_sibling(c), Some(b)); }
#[test] fn children_iterator() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); doc.append_child(root, b); doc.append_child(root, c);
let children: Vec<NodeId> = doc.children(root).collect(); assert_eq!(children, vec![a, b, c]); }
#[test] fn children_iterator_empty() { let doc = Document::new(); let children: Vec<NodeId> = doc.children(doc.root()).collect(); assert!(children.is_empty()); }
#[test] fn insert_before_first() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); doc.append_child(root, b); doc.insert_before(root, a, b);
let children: Vec<NodeId> = doc.children(root).collect(); assert_eq!(children, vec![a, b]); assert_eq!(doc.first_child(root), Some(a)); assert_eq!(doc.prev_sibling(b), Some(a)); assert_eq!(doc.next_sibling(a), Some(b)); }
#[test] fn insert_before_middle() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); doc.append_child(root, c); doc.insert_before(root, b, c);
let children: Vec<NodeId> = doc.children(root).collect(); assert_eq!(children, vec![a, b, c]); }
#[test] fn remove_child_only() { let mut doc = Document::new(); let root = doc.root(); let child = doc.create_element("div"); doc.append_child(root, child); doc.remove_child(root, child);
assert!(doc.parent(child).is_none()); assert!(doc.first_child(root).is_none()); assert!(doc.last_child(root).is_none()); }
#[test] fn remove_child_first() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); doc.append_child(root, a); doc.append_child(root, b); doc.remove_child(root, a);
assert_eq!(doc.first_child(root), Some(b)); assert_eq!(doc.last_child(root), Some(b)); assert!(doc.prev_sibling(b).is_none()); }
#[test] fn remove_child_last() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); doc.append_child(root, a); doc.append_child(root, b); doc.remove_child(root, b);
assert_eq!(doc.first_child(root), Some(a)); assert_eq!(doc.last_child(root), Some(a)); assert!(doc.next_sibling(a).is_none()); }
#[test] fn remove_child_middle() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); doc.append_child(root, b); doc.append_child(root, c); doc.remove_child(root, b);
let children: Vec<NodeId> = doc.children(root).collect(); assert_eq!(children, vec![a, c]); assert_eq!(doc.next_sibling(a), Some(c)); assert_eq!(doc.prev_sibling(c), Some(a)); }
#[test] fn append_child_moves_from_old_parent() { let mut doc = Document::new(); let root = doc.root(); let parent1 = doc.create_element("div"); let parent2 = doc.create_element("span"); let child = doc.create_element("p"); doc.append_child(root, parent1); doc.append_child(root, parent2); doc.append_child(parent1, child);
assert_eq!(doc.parent(child), Some(parent1));
// Move child from parent1 to parent2. doc.append_child(parent2, child);
assert_eq!(doc.parent(child), Some(parent2)); assert!(doc.first_child(parent1).is_none()); assert_eq!(doc.first_child(parent2), Some(child)); }
#[test] fn set_and_get_attribute() { let mut doc = Document::new(); let div = doc.create_element("div");
assert!(doc.get_attribute(div, "class").is_none());
doc.set_attribute(div, "class", "container"); assert_eq!(doc.get_attribute(div, "class"), Some("container"));
doc.set_attribute(div, "id", "main"); assert_eq!(doc.get_attribute(div, "id"), Some("main")); assert_eq!(doc.get_attribute(div, "class"), Some("container")); }
#[test] fn set_attribute_replaces() { let mut doc = Document::new(); let div = doc.create_element("div"); doc.set_attribute(div, "class", "old"); doc.set_attribute(div, "class", "new"); assert_eq!(doc.get_attribute(div, "class"), Some("new")); }
#[test] fn remove_attribute() { let mut doc = Document::new(); let div = doc.create_element("div"); doc.set_attribute(div, "class", "x"); assert!(doc.remove_attribute(div, "class")); assert!(doc.get_attribute(div, "class").is_none()); assert!(!doc.remove_attribute(div, "class")); }
#[test] fn attribute_on_non_element_is_noop() { let mut doc = Document::new(); let text = doc.create_text("hello"); doc.set_attribute(text, "class", "x"); assert!(doc.get_attribute(text, "class").is_none()); assert!(!doc.remove_attribute(text, "class")); }
#[test] fn text_content_set() { let mut doc = Document::new(); let t = doc.create_text("hello"); doc.set_text_content(t, "world"); assert_eq!(doc.text_content(t), Some("world")); }
#[test] fn text_content_on_element_is_none() { let mut doc = Document::new(); let div = doc.create_element("div"); assert!(doc.text_content(div).is_none()); }
#[test] fn build_simple_html_tree() { // Build: <html><head><title>Test</title></head><body><p>Hello</p></body></html> let mut doc = Document::new(); let root = doc.root();
let html = doc.create_element("html"); let head = doc.create_element("head"); let title = doc.create_element("title"); let title_text = doc.create_text("Test"); let body = doc.create_element("body"); let p = doc.create_element("p"); let p_text = doc.create_text("Hello");
doc.append_child(root, html); doc.append_child(html, head); doc.append_child(head, title); doc.append_child(title, title_text); doc.append_child(html, body); doc.append_child(body, p); doc.append_child(p, p_text);
// Verify structure. assert_eq!(doc.tag_name(html), Some("html")); assert_eq!(doc.parent(html), Some(root));
let html_children: Vec<NodeId> = doc.children(html).collect(); assert_eq!(html_children, vec![head, body]);
let head_children: Vec<NodeId> = doc.children(head).collect(); assert_eq!(head_children, vec![title]);
let title_children: Vec<NodeId> = doc.children(title).collect(); assert_eq!(title_children, vec![title_text]); assert_eq!(doc.text_content(title_text), Some("Test"));
let body_children: Vec<NodeId> = doc.children(body).collect(); assert_eq!(body_children, vec![p]);
let p_children: Vec<NodeId> = doc.children(p).collect(); assert_eq!(p_children, vec![p_text]); assert_eq!(doc.text_content(p_text), Some("Hello")); }
#[test] fn build_tree_with_attributes() { let mut doc = Document::new(); let root = doc.root();
let a = doc.create_element("a"); doc.set_attribute(a, "href", "https://example.com"); doc.set_attribute(a, "class", "link"); doc.append_child(root, a);
let text = doc.create_text("Click here"); doc.append_child(a, text);
assert_eq!(doc.get_attribute(a, "href"), Some("https://example.com")); assert_eq!(doc.get_attribute(a, "class"), Some("link")); assert_eq!(doc.text_content(text), Some("Click here")); }
#[test] fn nested_children_traversal() { // <div><span><em>deep</em></span></div> let mut doc = Document::new(); let root = doc.root();
let div = doc.create_element("div"); let span = doc.create_element("span"); let em = doc.create_element("em"); let text = doc.create_text("deep");
doc.append_child(root, div); doc.append_child(div, span); doc.append_child(span, em); doc.append_child(em, text);
// Walk down. let mut current = doc.first_child(root).unwrap(); assert_eq!(doc.tag_name(current), Some("div"));
current = doc.first_child(current).unwrap(); assert_eq!(doc.tag_name(current), Some("span"));
current = doc.first_child(current).unwrap(); assert_eq!(doc.tag_name(current), Some("em"));
let leaf = doc.first_child(current).unwrap(); assert_eq!(doc.text_content(leaf), Some("deep"));
// Walk back up. assert_eq!(doc.parent(leaf).map(|n| doc.tag_name(n)), Some(Some("em"))); }
#[test] fn document_len_and_is_empty() { let doc = Document::new(); assert_eq!(doc.len(), 1); // root only assert!(doc.is_empty()); // no children besides root
let mut doc2 = Document::new(); let _ = doc2.create_element("div"); assert_eq!(doc2.len(), 2); assert!(!doc2.is_empty()); }
#[test] fn default_document() { let doc = Document::default(); assert_eq!(doc.root(), NodeId(0)); }
#[test] fn node_id_equality() { let id1 = NodeId(5); let id2 = NodeId(5); let id3 = NodeId(6); assert_eq!(id1, id2); assert_ne!(id1, id3); }
#[test] #[should_panic(expected = "reference node is not a child of parent")] fn insert_before_wrong_parent_panics() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); let b = doc.create_element("b"); let c = doc.create_element("c"); doc.append_child(root, a); // b is not a child of root, so this should panic. doc.insert_before(root, c, b); }
#[test] #[should_panic(expected = "node is not a child of parent")] fn remove_child_wrong_parent_panics() { let mut doc = Document::new(); let root = doc.root(); let a = doc.create_element("a"); // a is not attached to root. doc.remove_child(root, a); }
#[test] fn insert_before_moves_from_old_parent() { let mut doc = Document::new(); let root = doc.root(); let parent1 = doc.create_element("div"); let parent2 = doc.create_element("span"); let child = doc.create_element("p"); let reference = doc.create_element("em");
doc.append_child(root, parent1); doc.append_child(root, parent2); doc.append_child(parent1, child); doc.append_child(parent2, reference);
// Move child from parent1 to parent2 before reference. doc.insert_before(parent2, child, reference);
assert_eq!(doc.parent(child), Some(parent2)); assert!(doc.first_child(parent1).is_none()); let children: Vec<NodeId> = doc.children(parent2).collect(); assert_eq!(children, vec![child, reference]); }
#[test] fn comment_in_tree() { let mut doc = Document::new(); let root = doc.root(); let comment = doc.create_comment("TODO: add content"); let div = doc.create_element("div"); doc.append_child(root, comment); doc.append_child(root, div);
let children: Vec<NodeId> = doc.children(root).collect(); assert_eq!(children, vec![comment, div]); assert_eq!(doc.text_content(comment), Some("TODO: add content")); }}