///|
/// CDP DOM Domain
///
/// Implements Chrome DevTools Protocol DOM domain.
/// Maps CDP operations to Core Primitives.
///|
/// DOM Domain handler
pub struct DomDomain {
tree : @dom.DomTree
}
///|
/// Create new DOM domain
pub fn DomDomain::new(tree : @dom.DomTree) -> DomDomain {
{ tree, }
}
// =============================================================================
// Document Operations (DOM.getDocument, DOM.requestChildNodes)
// =============================================================================
///|
/// DOM.getDocument - Returns the root DOM node
pub fn DomDomain::get_document(
self : DomDomain,
depth : Int?, // Optional depth to include
) -> Result[CdpNode, CdpError] {
let root = self.tree.get_document()
let max_depth = depth.unwrap_or(1)
self.node_to_cdp(root, 0, max_depth)
}
///|
/// DOM.requestChildNodes - Requests child nodes for the given node
pub fn DomDomain::request_child_nodes(
self : DomDomain,
node_id : Int,
depth : Int?,
) -> Result[Array[CdpNode], CdpError] {
let max_depth = depth.unwrap_or(1)
let node = @dom.NodeId::from_int(node_id)
match self.get_request_child_nodes(node) {
Ok(child_nodes) => {
let cdp_children : Array[CdpNode] = []
for child in child_nodes {
match self.node_to_cdp(child, 0, max_depth) {
Ok(cdp_node) => cdp_children.push(cdp_node)
Err(e) => return Err(e)
}
}
Ok(cdp_children)
}
Err(e) => Err(core_to_cdp_error(e))
}
}
// =============================================================================
// Query Operations (DOM.querySelector, DOM.querySelectorAll)
// =============================================================================
///|
/// DOM.querySelector - Executes querySelector on a given node
pub fn DomDomain::query_selector(
self : DomDomain,
node_id : Int,
selector : String,
) -> Result[Int?, CdpError] {
let node = @dom.NodeId::from_int(node_id)
match self.tree.query_selector(node, selector) {
Ok(result) =>
match result {
Some(found) => Ok(Some(found.to_int()))
None => Ok(None)
}
Err(e) => Err(core_to_cdp_error(e))
}
}
///|
/// DOM.querySelectorAll - Executes querySelectorAll on a given node
pub fn DomDomain::query_selector_all(
self : DomDomain,
node_id : Int,
selector : String,
) -> Result[Array[Int], CdpError] {
let node = @dom.NodeId::from_int(node_id)
match self.tree.query_selector_all(node, selector) {
Ok(results) => Ok(results.map(fn(n) { n.to_int() }))
Err(e) => Err(core_to_cdp_error(e))
}
}
// =============================================================================
// Attribute Operations
// =============================================================================
///|
/// DOM.getAttributes - Returns attributes for the given node
pub fn DomDomain::get_attributes(
self : DomDomain,
node_id : Int,
) -> Result[Array[String], CdpError] {
let node = @dom.NodeId::from_int(node_id)
match self.tree.get_attributes(node) {
Ok(attrs) => {
// CDP returns [name, value, name, value, ...]
let result : Array[String] = []
for pair in attrs {
result.push(pair.0)
result.push(pair.1)
}
Ok(result)
}
Err(e) => Err(core_to_cdp_error(e))
}
}
///|
/// DOM.setAttributeValue - Sets attribute value
pub fn DomDomain::set_attribute_value(
self : DomDomain,
node_id : Int,
name : String,
value : String,
) -> Result[Unit, CdpError] {
let node = @dom.NodeId::from_int(node_id)
match self.tree.set_attribute(node, name, value) {
Ok(_) => Ok(())
Err(e) => Err(core_to_cdp_error(e))
}
}
///|
/// DOM.removeAttribute - Removes attribute
pub fn DomDomain::remove_attribute(
self : DomDomain,
node_id : Int,
name : String,
) -> Result[Unit, CdpError] {
let node = @dom.NodeId::from_int(node_id)
match self.tree.remove_attribute(node, name) {
Ok(_) => Ok(())
Err(e) => Err(core_to_cdp_error(e))
}
}
// =============================================================================
// Node Operations
// =============================================================================
///|
/// DOM.getOuterHTML - Returns node's outer HTML
pub fn DomDomain::get_outer_html(
self : DomDomain,
node_id : Int,
) -> Result[String, CdpError] {
let node = @dom.NodeId::from_int(node_id)
match self.tree.get_node_info(node) {
Ok(info) =>
match info.node_type {
@dom.Text => Ok(info.node_value)
@dom.Element | @dom.DocumentFragment | @dom.ShadowRoot => {
// Build outer HTML
let buf = StringBuilder::new()
self.build_outer_html(node, buf)
Ok(buf.to_string())
}
_ => Ok("")
}
Err(e) => Err(core_to_cdp_error(e))
}
}
///|
/// DOM.setNodeValue - Sets node value (for text nodes)
pub fn DomDomain::set_node_value(
self : DomDomain,
node_id : Int,
value : String,
) -> Result[Unit, CdpError] {
let node = @dom.NodeId::from_int(node_id)
match self.tree.set_text_content(node, value) {
Ok(_) => Ok(())
Err(e) => Err(core_to_cdp_error(e))
}
}
///|
/// DOM.removeNode - Removes node from the tree
pub fn DomDomain::remove_node(
self : DomDomain,
node_id : Int,
) -> Result[Unit, CdpError] {
let node = @dom.NodeId::from_int(node_id)
match self.tree.get_parent(node) {
Ok(Some(parent)) =>
match self.tree.remove_child(parent, node) {
Ok(_) => Ok(())
Err(e) => Err(core_to_cdp_error(e))
}
Ok(None) =>
Err(CdpError::from_code(InvalidParams, "Cannot remove root node"))
Err(e) => Err(core_to_cdp_error(e))
}
}
// =============================================================================
// Box Model
// =============================================================================
///|
/// DOM.getBoxModel - Returns box model for the given node
pub fn DomDomain::get_box_model(
self : DomDomain,
node_id : Int,
) -> Result[CdpBoxModel, CdpError] {
let node = @dom.NodeId::from_int(node_id)
match self.tree.get_cached_rect(node) {
Some(rect) => {
// Convert Rect to box model (simplified - all boxes same)
let quad = [
rect.x,
rect.y,
rect.x + rect.width,
rect.y,
rect.x + rect.width,
rect.y + rect.height,
rect.x,
rect.y + rect.height,
]
Ok({
content: quad,
padding: quad,
border: quad,
margin: quad,
width: rect.width.to_int(),
height: rect.height.to_int(),
})
}
None =>
// Return zero box if no layout computed
Ok({
content: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
padding: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
border: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
margin: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
width: 0,
height: 0,
})
}
}
// =============================================================================
// Internal Helpers
// =============================================================================
///|
/// Convert core NodeId to CDP node representation
fn DomDomain::node_to_cdp(
self : DomDomain,
node : @dom.NodeId,
current_depth : Int,
max_depth : Int,
) -> Result[CdpNode, CdpError] {
match self.tree.get_node_info(node) {
Ok(info) => {
let node_type = match info.node_type {
@dom.Document => 9
@dom.Element => 1
@dom.Text => 3
@dom.Comment => 8
@dom.DocumentType => 10
@dom.DocumentFragment => 11
@dom.ShadowRoot => 11
}
// Get attributes for elements
let attributes : Array[String]? = match info.node_type {
@dom.Element =>
match self.tree.get_attributes(node) {
Ok(attrs) => {
let arr : Array[String] = []
for pair in attrs {
arr.push(pair.0)
arr.push(pair.1)
}
Some(arr)
}
Err(_) => None
}
_ => None
}
let shadow_roots : Array[CdpNode]? = match
self.tree.get_shadow_root(node) {
Ok(Some(shadow_root)) =>
match self.node_to_cdp(shadow_root, current_depth + 1, max_depth) {
Ok(cdp_shadow_root) => Some([cdp_shadow_root])
Err(_) => None
}
_ => None
}
let shadow_root_type = match info.node_type {
@dom.ShadowRoot => self.tree.get_shadow_mode(node).unwrap_or(None)
_ => None
}
// Get children if within depth limit
let children : Array[CdpNode]? = if current_depth < max_depth {
match self.tree.get_children(node) {
Ok(child_nodes) => {
let cdp_children : Array[CdpNode] = []
for child in child_nodes {
match self.node_to_cdp(child, current_depth + 1, max_depth) {
Ok(cdp_child) => cdp_children.push(cdp_child)
Err(_) => () // Skip failed children
}
}
Some(cdp_children)
}
Err(_) => None
}
} else {
None
}
Ok({
node_id: node.to_int(),
backend_node_id: node.to_int(),
node_type,
node_name: info.node_name.to_upper(),
local_name: info.node_name.to_lower(),
node_value: info.node_value,
child_node_count: Some(info.child_count),
children,
attributes,
shadow_roots,
shadow_root_type,
})
}
Err(e) => Err(core_to_cdp_error(e))
}
}
///|
/// Build outer HTML recursively
fn DomDomain::build_outer_html(
self : DomDomain,
node : @dom.NodeId,
buf : StringBuilder,
) -> Unit {
match self.tree.get_node_info(node) {
Ok(info) =>
match info.node_type {
@dom.Text => buf.write_string(info.node_value)
@dom.Comment => {
buf.write_string("")
}
@dom.Element => {
buf.write_string("<")
buf.write_string(info.node_name)
// Add attributes
match self.tree.get_attributes(node) {
Ok(attrs) =>
for pair in attrs {
buf.write_string(" ")
buf.write_string(pair.0)
buf.write_string("=\"")
buf.write_string(pair.1)
buf.write_string("\"")
}
Err(_) => ()
}
buf.write_string(">")
// Add children
match self.tree.get_children(node) {
Ok(children) =>
for child in children {
self.build_outer_html(child, buf)
}
Err(_) => ()
}
// Close tag
buf.write_string("")
buf.write_string(info.node_name)
buf.write_string(">")
}
@dom.DocumentFragment | @dom.ShadowRoot =>
match self.tree.get_children(node) {
Ok(children) =>
for child in children {
self.build_outer_html(child, buf)
}
Err(_) => ()
}
_ => ()
}
Err(_) => ()
}
}
///|
fn DomDomain::get_request_child_nodes(
self : DomDomain,
node : @dom.NodeId,
) -> Result[Array[@dom.NodeId], @dom.CoreError] {
match self.tree.get_children(node) {
Ok(children) =>
match self.tree.get_shadow_root(node) {
Ok(Some(shadow_root)) => {
let result = children.copy()
result.push(shadow_root)
Ok(result)
}
Ok(None) => Ok(children)
Err(err) => Err(err)
}
Err(err) => Err(err)
}
}