fix(cdp): wrap role/name/value as CDP AXValue objects
Per qodo-ai-reviewer feedback on PR #44: Accessibility.getFullAXTree was emitting AXNode.role as a plain string and name/value as plain strings or null, instead of CDP-compliant AXValue objects. Changes: - Add ax_value_role() and ax_value_string() helpers - Change compute_name()/compute_value() to return Option<String> - Wrap role, name, value as AXValue in build_ax_node() The properties array already used the correct AXValue format.
This commit is contained in:
1 parent
a4591707e3
commit
266afa7c34
1 file changed
+22
-12
@@ -3,6 +3,16 @@ use serde_json::{json, Value};
|
|||||||
|
|
||||||
use crate::dispatch::CdpContext;
|
use crate::dispatch::CdpContext;
|
||||||
|
|
||||||
|
/// Build a CDP AXValue for a role type.
|
||||||
|
fn ax_value_role(role: &str) -> Value {
|
||||||
|
json!({"type": "role", "value": role})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a CDP AXValue for a string type.
|
||||||
|
fn ax_value_string(s: &str) -> Value {
|
||||||
|
json!({"type": "string", "value": s})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle(
|
pub async fn handle(
|
||||||
method: &str,
|
method: &str,
|
||||||
_params: &Value,
|
_params: &Value,
|
||||||
@@ -88,9 +98,9 @@ fn build_ax_node(
|
|||||||
Some(json!({
|
Some(json!({
|
||||||
"nodeId": ax_id,
|
"nodeId": ax_id,
|
||||||
"ignored": false,
|
"ignored": false,
|
||||||
"role": role,
|
"role": ax_value_role(role),
|
||||||
"name": name,
|
"name": name.as_deref().map(ax_value_string),
|
||||||
"value": value,
|
"value": value.as_deref().map(ax_value_string),
|
||||||
"properties": properties,
|
"properties": properties,
|
||||||
"childIds": child_ids,
|
"childIds": child_ids,
|
||||||
"backendDOMNodeId": node_id.raw(),
|
"backendDOMNodeId": node_id.raw(),
|
||||||
@@ -215,7 +225,7 @@ fn map_role(data: &NodeData) -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the accessible name for a node.
|
/// Compute the accessible name for a node.
|
||||||
fn compute_name(dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
fn compute_name(dom: &DomTree, node: &obscura_dom::Node) -> Option<String> {
|
||||||
if let NodeData::Element { attrs, .. } = &node.data {
|
if let NodeData::Element { attrs, .. } = &node.data {
|
||||||
// aria-label takes highest priority
|
// aria-label takes highest priority
|
||||||
if let Some(label) = attrs
|
if let Some(label) = attrs
|
||||||
@@ -223,7 +233,7 @@ fn compute_name(dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
|||||||
.find(|a| a.name.local.as_ref() == "aria-label")
|
.find(|a| a.name.local.as_ref() == "aria-label")
|
||||||
.map(|a| a.value.clone())
|
.map(|a| a.value.clone())
|
||||||
{
|
{
|
||||||
return Some(json!(label));
|
return Some(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
// aria-labelledby
|
// aria-labelledby
|
||||||
@@ -241,7 +251,7 @@ fn compute_name(dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
|||||||
}
|
}
|
||||||
let trimmed = name.trim().to_string();
|
let trimmed = name.trim().to_string();
|
||||||
if !trimmed.is_empty() {
|
if !trimmed.is_empty() {
|
||||||
return Some(json!(trimmed));
|
return Some(trimmed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +262,7 @@ fn compute_name(dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
|||||||
.map(|a| a.value.clone())
|
.map(|a| a.value.clone())
|
||||||
{
|
{
|
||||||
if !alt.is_empty() {
|
if !alt.is_empty() {
|
||||||
return Some(json!(alt));
|
return Some(alt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +273,7 @@ fn compute_name(dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
|||||||
.map(|a| a.value.clone())
|
.map(|a| a.value.clone())
|
||||||
{
|
{
|
||||||
if !title.is_empty() {
|
if !title.is_empty() {
|
||||||
return Some(json!(title));
|
return Some(title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +284,7 @@ fn compute_name(dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
|||||||
.map(|a| a.value.clone())
|
.map(|a| a.value.clone())
|
||||||
{
|
{
|
||||||
if !placeholder.is_empty() {
|
if !placeholder.is_empty() {
|
||||||
return Some(json!(placeholder));
|
return Some(placeholder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +293,7 @@ fn compute_name(dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
|||||||
if let NodeData::Text { contents } = &node.data {
|
if let NodeData::Text { contents } = &node.data {
|
||||||
let trimmed = contents.trim().to_string();
|
let trimmed = contents.trim().to_string();
|
||||||
if !trimmed.is_empty() {
|
if !trimmed.is_empty() {
|
||||||
return Some(json!(trimmed));
|
return Some(trimmed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +301,7 @@ fn compute_name(dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the accessible value for a node (e.g., current input value).
|
/// Compute the accessible value for a node (e.g., current input value).
|
||||||
fn compute_value(_dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
fn compute_value(_dom: &DomTree, node: &obscura_dom::Node) -> Option<String> {
|
||||||
if let NodeData::Element { name, attrs, .. } = &node.data {
|
if let NodeData::Element { name, attrs, .. } = &node.data {
|
||||||
let tag = name.local.as_ref();
|
let tag = name.local.as_ref();
|
||||||
// For input elements, return the value attribute
|
// For input elements, return the value attribute
|
||||||
@@ -299,7 +309,7 @@ fn compute_value(_dom: &DomTree, node: &obscura_dom::Node) -> Option<Value> {
|
|||||||
if let Some(val) = attrs
|
if let Some(val) = attrs
|
||||||
.iter()
|
.iter()
|
||||||
.find(|a| a.name.local.as_ref() == "value")
|
.find(|a| a.name.local.as_ref() == "value")
|
||||||
.map(|a| json!(a.value.clone()))
|
.map(|a| a.value.clone())
|
||||||
{
|
{
|
||||||
return Some(val);
|
return Some(val);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user