feat(extension): add bsk hover and adjust vom to support

This commit is contained in:
Ljy-0827
2026-08-06 12:19:45 +08:00
parent 0f264c5df1
commit 91225e5fdb
31 changed files with 1051 additions and 47 deletions
@@ -77,6 +77,8 @@ fn main() {
dump!(ClickParams, "tool_click_params");
dump!(ClickResult, "tool_click_result");
dump!(HoverParams, "tool_hover_params");
dump!(HoverResult, "tool_hover_result");
dump!(FillParams, "tool_fill_params");
dump!(FillResult, "tool_fill_result");
dump!(PressParams, "tool_press_params");
+6 -1
View File
@@ -70,6 +70,8 @@ pub enum Method {
ToolReload,
#[serde(rename = "tool.click")]
ToolClick,
#[serde(rename = "tool.hover")]
ToolHover,
#[serde(rename = "tool.fill")]
ToolFill,
#[serde(rename = "tool.press")]
@@ -161,7 +163,7 @@ impl Method {
// Transient input — no committed browser action, but still page
// input. It must be stopped by pending user interrupts.
Method::ToolObserve => MethodEffect::TransientInput,
Method::ToolHover | Method::ToolObserve => MethodEffect::TransientInput,
// Passive reads — transparent.
// `record_stop` / `record_await` observe / finish a recording
@@ -259,6 +261,7 @@ mod tests {
fn is_mutating_classifies_read_only_tools_as_non_mutating() {
assert!(!Method::ToolTabList.is_mutating());
assert!(!Method::ToolSnapshot.is_mutating());
assert!(!Method::ToolHover.is_mutating());
assert!(!Method::ToolObserve.is_mutating());
assert!(!Method::ToolGetHtml.is_mutating());
assert!(!Method::ToolScreenshot.is_mutating());
@@ -321,6 +324,7 @@ mod tests {
#[test]
fn effect_classifies_observe_as_transient_input() {
assert_eq!(Method::ToolSnapshot.effect(), MethodEffect::PassiveRead);
assert_eq!(Method::ToolHover.effect(), MethodEffect::TransientInput);
assert_eq!(Method::ToolObserve.effect(), MethodEffect::TransientInput);
assert_eq!(Method::ToolClick.effect(), MethodEffect::BrowserMutation);
assert_eq!(Method::Cancel.effect(), MethodEffect::ControlPlane);
@@ -329,6 +333,7 @@ mod tests {
#[test]
fn interrupt_gate_includes_transient_input() {
assert!(!Method::ToolSnapshot.requires_interrupt_gate());
assert!(Method::ToolHover.requires_interrupt_gate());
assert!(Method::ToolObserve.requires_interrupt_gate());
assert!(Method::ToolClick.requires_interrupt_gate());
assert!(!Method::Cancel.requires_interrupt_gate());
@@ -85,6 +85,52 @@ pub struct ClickResult {
pub dialogs: Vec<JavaScriptDialogInfo>,
}
// ---------------------------------------------------------------------------
// hover
// ---------------------------------------------------------------------------
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct HoverParams {
pub session_id: String,
/// Optional `@e<N>` ref allocated by the last observation.
/// Mutually exclusive with `selector`.
#[serde(
rename = "ref",
alias = "ref_",
default,
skip_serializing_if = "Option::is_none"
)]
pub ref_: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selector: Option<String>,
/// Target tab. Defaults to the Agent Window's active tab.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tab_id: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub modifiers: Option<Vec<KeyModifier>>,
/// Wait after the mouse move so hover-triggered UI can settle.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 0))]
pub settle_ms: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 1))]
pub timeout_ms: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct HoverResult {
pub tab_id: i64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub used_ref: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub used_selector: Option<String>,
/// Viewport-relative hover coordinates (CSS pixels).
pub x: f64,
pub y: f64,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dialogs: Vec<JavaScriptDialogInfo>,
}
// ---------------------------------------------------------------------------
// fill
// ---------------------------------------------------------------------------
@@ -261,6 +307,24 @@ mod tests {
assert_eq!(p.ref_.as_deref(), Some("e1"));
}
#[test]
fn hover_params_serialise_ref_field_name() {
let p = HoverParams {
session_id: "abcd".into(),
ref_: Some("@e3".into()),
selector: None,
tab_id: Some(42),
modifiers: Some(vec![KeyModifier::Shift]),
settle_ms: Some(200),
timeout_ms: Some(5_000),
};
let v = serde_json::to_value(&p).unwrap();
assert_eq!(v.get("ref").and_then(|v| v.as_str()), Some("@e3"));
assert!(v.get("ref_").is_none());
let round: HoverParams = serde_json::from_value(v).unwrap();
assert_eq!(round, p);
}
#[test]
fn modifiers_render_as_lowercase_strings() {
let v = serde_json::to_value(KeyModifier::Ctrl).unwrap();
+5
View File
@@ -94,6 +94,11 @@ pub enum Step {
common: StepCommon,
target: TargetDescriptor,
},
Hover {
#[serde(flatten)]
common: StepCommon,
target: TargetDescriptor,
},
Fill {
#[serde(flatten)]
common: StepCommon,