DOM-style API
wgpu-html exposes a browser-inspired DOM API on the Tree and Node types. All query methods are synchronous and walk the in-memory tree.
Tree-level Query Methods
All methods on Tree operate from the document root:
get_element_by_id(id: &str) -> Option<&mut Node>
Depth-first search for the first element with matching id attribute. Returns a mutable reference so callbacks and mutations can be attached.
if let Some(btn) = tree.get_element_by_id("submit-btn") {
btn.on_click = Some(Arc::new(|ev| {
println!("Submit clicked!");
}));
}
get_element_by_class_name(class_name: &str) -> Option<&Node>
First match by class token (whitespace-separated matching).
if let Some(card) = tree.get_element_by_class_name("card") {
println!("First card found");
}
get_elements_by_class_name(class_name: &str) -> Vec<&Node>
All matching elements, document order.
let all_active = tree.get_elements_by_class_name("active");
eprintln!("{} active elements", all_active.len());
get_element_by_name(name: &str) -> Option<&Node>
First element whose name attribute matches. Useful for form controls.
if let Some(username_input) = tree.get_element_by_name("username") {
// Found the input with name="username"
}
get_elements_by_name(name: &str) -> Vec<&Node>
All elements with matching name attribute (case-sensitive).
let radios = tree.get_elements_by_name("color");
// All radio buttons with name="color"
get_element_by_tag_name(tag_name: &str) -> Option<&Node>
First element matching the tag name (case-insensitive).
if let Some(form) = tree.get_element_by_tag_name("form") {
// First <form> in the document
}
get_elements_by_tag_name(tag_name: &str) -> Vec<&Node>
All elements matching the tag name.
let all_divs = tree.get_elements_by_tag_name("div");
Path-returning Variants
The find_elements_by_* family returns Vec<Vec<usize>> — child-index paths instead of references:
let paths: Vec<Vec<usize>> = tree.find_elements_by_class_name("card");
for path in &paths {
if let Some(node) = tree.root.as_ref()?.at_path(path) {
println!("Card at path {:?}: {:?}", path, node.element.tag_name());
}
}
Available:
find_elements_by_class_name(name) -> Vec<Vec<usize>>find_elements_by_name(name) -> Vec<Vec<usize>>find_elements_by_tag_name(name) -> Vec<Vec<usize>>
Paths are useful for passing to at_path(), at_path_mut(), node_to_html(path), and event dispatch functions.
CSS Selector Queries
The query module (wgpu-html-tree/src/query.rs) provides full CSS selector support for runtime queries:
query_selector(sel: &str) -> Option<&Node>
// Single match (first in document order)
if let Some(target) = tree.query_selector(".sidebar > .active") {
eprintln!("Active sidebar item: {:?}", target.element.tag_name());
}
query_selector_all(sel: &str) -> Vec<&Node>
// All matches
let disabled_inputs = tree.query_selector_all("input:disabled");
let odd_rows = tree.query_selector_all("tr:nth-child(odd)");
let has_img = tree.query_selector_all("li:has(img)");
matches(sel: &str) -> bool
if let Some(node) = tree.query_selector("#main") {
if node.matches(".visible") {
// ...
}
}
closest(sel: &str) -> Option<&Node>
Walks up the ancestor chain:
if let Some(node) = tree.query_selector("#nested") {
// Find the nearest ancestor <section>
if let Some(section) = node.closest("section") {
eprintln!("Inside section: {:?}", section.element.id());
}
}
The query engine supports a superset of the stylesheet parser's selectors:
- Combinators: descendant (
), child (>), next-sibling (+), subsequent-sibling (~) - Attribute selectors:
[attr],[attr=val],[attr~=val],[attr|=val],[attr^=val],[attr$=val],[attr*=val], with optionalicase flag - Pseudo-classes:
:hover,:active,:focus,:focus-within,:checked,:disabled,:enabled,:required,:optional,:read-only,:read-write,:placeholder-shown,:first-child,:last-child,:only-child,:first-of-type,:last-of-type,:nth-child(),:nth-last-child(),:nth-of-type(),:not(),:is(),:where(),:has(),:root,:scope,:lang(),:dir() - Selector lists: comma-separated (
a, b, c) - Pseudo-elements (
::before,::after) parsed but never match
Node API
Tree Traversal
// Walk from root
let html_node = &tree.root.unwrap();
let body = &html_node.children[0];
let first_child = &body.children[0];
// Path-based access
if let Some(node) = root.at_path(&[0, 2, 1]) {
// node = root.children[0].children[2].children[1]
eprintln!("Found: {}", node.element.tag_name());
}
// Walk path to root (useful for event bubbling)
let chain: Vec<&mut Node> = root.ancestry_at_path_mut(&[0, 2, 1]);
// chain[0] = deepest, chain.last() = root
Accessing Element Data
let node: &Node = /* ... */;
// Tag name
println!("Tag: {}", node.element.tag_name());
// ID, class
if let Some(id) = node.element.id() {
println!("ID: {}", id);
}
if let Some(class) = node.element.class() {
println!("Classes: {}", class);
}
// Any attribute
if let Some(href) = node.element.attr("href") {
println!("Link: {}", href);
}
// Data attribute
if let Some(user_id) = node.element.attr("data-user-id") {
println!("User: {}", user_id);
}
// Boolean attributes
if node.element.attr("disabled").is_some() {
println!("Element is disabled");
}
Reading Layout Rectangles
After the layout pass completes, every node's content-box position is cached in Node::rect:
#[derive(Debug, Clone, Copy)]
pub struct NodeRect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
// After paint/layout:
if let Some(rect) = node.rect {
println!("Node at ({}, {}), size {}×{}",
rect.x, rect.y, rect.width, rect.height);
}
Store this rect after calling paint_tree / compute_layout — it is populated during the layout pass and unavailable before.
Children
for child in &node.children {
eprintln!("Child tag: {}", child.element.tag_name());
}
let count = node.children.len();
let first = node.children.first();
let last = node.children.last();
Custom Properties
Programmatic CSS custom properties can be set on any node:
node.set_custom_property("--card-bg", "#f5f5f5");
if let Some(color) = node.custom_property("--card-bg") {
eprintln!("Card background override: {}", color);
}
node.remove_custom_property("--card-bg");
Custom properties inherit to descendants and participate in var(--card-bg) resolution during cascade.
Tree API
Root Node
if let Some(root) = &tree.root {
// Walk root
}
Document Structure
// Access the <html> element
if let Some(html) = tree.root.as_ref() {
assert_eq!(html.element.tag_name(), "html");
let head = &html.children[0]; // <head> typically
let body = &html.children[1]; // <body> typically
}
Modifying the Tree Programmatically
// Insert a new node
let new_div = Node::new(Element::Div(m::Div::default()));
tree.insert_node(&[0, 1], 0, new_div); // parent_path, index, node
// Append a child
let new_p = Node::new(Element::P(m::P::default()));
tree.append_node(&[0, 1], new_p);
// Remove a node
if let Some(removed) = tree.remove_node(&[0, 1, 0]) {
eprintln!("Removed: {}", removed.element.tag_name());
}
// Template content
if let Some(range) = tree.append_template_content_to_id("row-tmpl", "table-body") {
eprintln!("Inserted {} children", range.len());
}
Focus and Active Element
// Focus management
wgpu_html_tree::focus(&mut tree, &[0, 1, 3]); // Focus a specific path
wgpu_html_tree::focus(&mut tree, &[]); // Focus root
wgpu_html_tree::blur(&mut tree); // Clear focus
wgpu_html_tree::focus_next(&mut tree, false); // Tab forward
wgpu_html_tree::focus_next(&mut tree, true); // Shift+Tab backward
// Read active element
if let Some(active) = tree.active_element() {
eprintln!("Focused: {}", active.element.tag_name());
}
if let Some(value) = tree.active_element().and_then(|n| n.element.attr("value")) {
eprintln!("Focused value: {}", value);
}
Interaction State
// Hover status
if let Some(hovered) = tree.hovered_element() {
eprintln!("Hovered: {}", hovered.element.tag_name());
}
if tree.is_hovered(&[0, 1, 2]) {
eprintln!("Element at [0,1,2] is in hover chain");
}
// Cursor position
if let Some((x, y)) = tree.cursor_position() {
eprintln!("Cursor at ({}, {})", x, y);
}
DOM Walking Example
use wgpu_html_parser::parse;
use wgpu_html_tree::{Element, Node};
let mut tree = parse(r#"
<!DOCTYPE html>
<html>
<body>
<div id="sidebar" class="panel">
<ul>
<li class="nav-item active"><a href="/home">Home</a></li>
<li class="nav-item"><a href="/about">About</a></li>
</ul>
</div>
<main id="content">
<h1 class="title">Welcome</h1>
<p>Content here</p>
</main>
</body>
</html>
"#);
// Find by ID
let sidebar = tree.get_element_by_id("sidebar").unwrap();
assert_eq!(sidebar.element.tag_name(), "div");
// Query selector
let active_link = tree.query_selector("#sidebar .active a").unwrap();
assert_eq!(active_link.element.attr("href").as_deref(), Some("/home"));
// Walk children
for nav_item in tree.get_elements_by_class_name("nav-item") {
if let Some(link) = &nav_item.children.first() {
eprintln!("Nav link: {:?}", link.element.attr("href"));
}
}
// Path-based access
let body_path = &[0, 1]; // html.children[0] = head, html.children[1] = body
let main_path = &[0, 1, 1]; // body.children[1] = main
if let Some(main) = tree.root.as_ref().unwrap().at_path(main_path) {
assert_eq!(main.element.id(), Some("content"));
}
See Also
- HTML Overview — The big picture of how HTML integrates with the pipeline
- Elements — Detailed per-element attribute documentation
- Interactivity — Event dispatch and mouse/keyboard handling