browser_engine 0.1.0

A modular, GPU-accelerated, security-first web browser engine built from scratch in Rust.
Documentation
pub mod builder;
pub mod node;

pub use builder::build_dom;
pub use node::{Dom, DomNode, ElementData, NodeType};

#[cfg(test)]
mod tests {
    use super::*;
    use crate::html::Tokenizer;

    #[test]
    fn test_input_and_button_parsing() {
        let html = r#"<form><input type="text" value="search"><button>Submit</button></form>"#;
        let tokens = Tokenizer::new(html).tokenize();
        let dom = build_dom(tokens);
        assert!(dom
            .nodes
            .iter()
            .any(|n| matches!(&n.node_type, NodeType::Element(e) if e.tag == "input")));
        assert!(dom
            .nodes
            .iter()
            .any(|n| matches!(&n.node_type, NodeType::Element(e) if e.tag == "button")));
    }

    #[test]
    fn test_dom_query_by_id_tag_class() {
        let html = r#"<main id="app"><div class="container active"><p class="text">Hello</p><span class="text">World</span></div></main>"#;
        let tokens = Tokenizer::new(html).tokenize();
        let dom = build_dom(tokens);

        let app_idx = dom.get_element_by_id("app").expect("app element exists");
        let app_elem = dom.element_at(app_idx).expect("is element");
        assert_eq!(app_elem.tag, "main");
        assert!(app_elem.has_attribute("id"));
        assert_eq!(app_elem.get_attribute("id"), Some("app"));

        let p_nodes = dom.get_elements_by_tag_name("p");
        assert_eq!(p_nodes.len(), 1);
        assert_eq!(dom.text_content(p_nodes[0]), "Hello");

        let text_nodes = dom.get_elements_by_class_name("text");
        assert_eq!(text_nodes.len(), 2);
    }

    #[test]
    fn test_dom_traversal_and_text_content() {
        let html = r#"<section id="s1"><h1 class="title">Header</h1><p>Paragraph <span>bold</span> text</p></section>"#;
        let tokens = Tokenizer::new(html).tokenize();
        let dom = build_dom(tokens);

        let s1 = dom.get_element_by_id("s1").unwrap();
        let children = dom.children(s1);
        assert_eq!(children.len(), 2);

        let h1_idx = children[0];
        let p_idx = children[1];

        assert_eq!(dom.first_child(s1), Some(h1_idx));
        assert_eq!(dom.last_child(s1), Some(p_idx));
        assert_eq!(dom.next_sibling(h1_idx), Some(p_idx));
        assert_eq!(dom.preceding_sibling(p_idx), Some(h1_idx));
        assert_eq!(dom.parent(h1_idx), Some(s1));

        let text = dom.text_content(s1);
        assert!(text.contains("Header"));
        assert!(text.contains("Paragraph bold text"));
    }

    #[test]
    fn test_dom_query_selector() {
        let html = r#"<div id="container"><div class="card"><p class="desc">First Card</p></div><div class="card active"><p class="desc">Second Card</p></div></div>"#;
        let tokens = Tokenizer::new(html).tokenize();
        let dom = build_dom(tokens);

        let first_card = dom.query_selector("div.card").expect("matches first card");
        assert_eq!(dom.element_at(first_card).unwrap().tag, "div");

        let active_card = dom
            .query_selector("div.card.active")
            .expect("matches active card");
        assert!(dom.element_at(active_card).unwrap().has_class("active"));

        let all_cards = dom.query_selector_all("div.card");
        assert_eq!(all_cards.len(), 2);

        let desc_all = dom.query_selector_all("p.desc");
        assert_eq!(desc_all.len(), 2);
        assert_eq!(dom.text_content(desc_all[0]), "First Card");
        assert_eq!(dom.text_content(desc_all[1]), "Second Card");
    }

    #[test]
    fn test_dom_query_selector_pseudo_classes() {
        let html = r#"<ul id="list"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul><button disabled>Off</button><button id="btn" data-hover>Hover Me</button>"#;
        let tokens = Tokenizer::new(html).tokenize();
        let mut dom = build_dom(tokens);

        let first = dom.query_selector("li:first-child").expect("first-child matches");
        assert_eq!(dom.text_content(first), "Item 1");

        let last = dom.query_selector("li:last-child").expect("last-child matches");
        assert_eq!(dom.text_content(last), "Item 3");

        let disabled_btn = dom.query_selector("button:disabled").expect("disabled matches");
        assert_eq!(dom.text_content(disabled_btn), "Off");

        let hovered_btn = dom.query_selector("button:hover").expect("hover matches");
        assert_eq!(dom.element_at(hovered_btn).unwrap().id.as_deref(), Some("btn"));

        let item2 = dom.query_selector_all("li")[1];
        dom.set_hovered(item2, true);
        assert!(crate::style::matching::matches_selector(
            &dom,
            item2,
            &crate::css::parser::parse_selector("li:hover").unwrap()
        ));
    }
}