dom-cat 0.1.0

Persistent DOM model: arena-backed Node tree with mutation API and CSS-selector matching. Consumes html-cat trees; selectors via css-cat. No mut, no Rc/Arc, no interior mutability, no panics, exhaustive matches. Third sub-crate of a Servo-replacement webview runtime targeting Tauri.
//! `querySelector` / `querySelectorAll`.

use css_cat::SelectorList;

use crate::document::Document;
use crate::error::Error;
use crate::matcher;
use crate::node::{Node, NodeId};
use crate::selector_parse::parse_selectors;

/// Return the first node (document order, starting from the root)
/// matching `selector_source`.
///
/// # Errors
///
/// Propagates CSS-parser errors and surfaces an
/// [`Error::InvalidSelector`] when no usable selector survives parsing.
pub fn query_selector(doc: &Document, selector_source: &str) -> Result<Option<NodeId>, Error> {
    let selectors = parse_selectors(selector_source)?;
    let candidates = walk_document_order(doc, doc.root(), Vec::new());
    Ok(candidates
        .into_iter()
        .find(|id| any_selector_matches(doc, *id, &selectors)))
}

/// Return all nodes matching `selector_source` in document order.
///
/// # Errors
///
/// Propagates CSS-parser errors and surfaces an
/// [`Error::InvalidSelector`] when no usable selector survives parsing.
pub fn query_selector_all(doc: &Document, selector_source: &str) -> Result<Vec<NodeId>, Error> {
    let selectors = parse_selectors(selector_source)?;
    let candidates = walk_document_order(doc, doc.root(), Vec::new());
    Ok(candidates
        .into_iter()
        .filter(|id| any_selector_matches(doc, *id, &selectors))
        .collect())
}

fn any_selector_matches(doc: &Document, id: NodeId, list: &SelectorList) -> bool {
    list.selectors()
        .iter()
        .any(|sel| matcher::matches(doc, id, sel))
}

fn walk_document_order(doc: &Document, start: NodeId, acc: Vec<NodeId>) -> Vec<NodeId> {
    let with_self = if matches!(doc.get(start), Some(Node::Element(_))) {
        acc.into_iter().chain(std::iter::once(start)).collect()
    } else {
        acc
    };
    doc.get(start)
        .map_or(&[][..], Node::children)
        .iter()
        .fold(with_self, |a, child| walk_document_order(doc, *child, a))
}