Add webview support for Electron apps in native mode (#671)

* Add webview support for Electron apps in native mode

Fixes #580

* Fix cargo fmt violations in actions.rs and browser.rs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Revert unrelated refactors, keep only webview support changes

- Restore is_none_or (was changed to map_or unnecessarily)
- Restore single-if WebDriver check (was split into nested ifs)
- Restore simple needs_relaunch logic (was expanded into 4 branches)
- Restore find_frame signature (unused selector param was added)
- Restore flat download handler condition (was nested unnecessarily)
- Restore wait_or_kill and graceful close() to preserve cookie flushing

---------

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Tate
2026-03-08 17:41:40 -05:00
committed by GitHub
co-authored by ctate Claude Opus 4.6
parent 2bab729f26
commit a0bd0c2f0f
3 changed files with 39 additions and 2 deletions
+5 -1
View File
@@ -167,7 +167,8 @@ impl DaemonState {
if let Ok(te) = if let Ok(te) =
serde_json::from_value::<TargetCreatedEvent>(event.params.clone()) serde_json::from_value::<TargetCreatedEvent>(event.params.clone())
{ {
if te.target_info.target_type == "page" if (te.target_info.target_type == "page"
|| te.target_info.target_type == "webview")
&& !te.target_info.url.is_empty() && !te.target_info.url.is_empty()
{ {
let already_tracked = self let already_tracked = self
@@ -443,6 +444,7 @@ pub async fn execute_command(cmd: &Value, state: &mut DaemonState) -> Value {
session_id: attach.session_id, session_id: attach.session_id,
url: te.target_info.url.clone(), url: te.target_info.url.clone(),
title: te.target_info.title.clone(), title: te.target_info.title.clone(),
target_type: te.target_info.target_type.clone(),
}); });
} }
} }
@@ -2468,6 +2470,7 @@ async fn handle_recording_start(cmd: &Value, state: &mut DaemonState) -> Result<
session_id: new_session_id.clone(), session_id: new_session_id.clone(),
url: nav_url.clone(), url: nav_url.clone(),
title: String::new(), title: String::new(),
target_type: "page".to_string(),
}); });
// Navigate to URL // Navigate to URL
@@ -4057,6 +4060,7 @@ async fn handle_window_new(cmd: &Value, state: &mut DaemonState) -> Result<Value
session_id: attach.session_id, session_id: attach.session_id,
url: "about:blank".to_string(), url: "about:blank".to_string(),
title: String::new(), title: String::new(),
target_type: "page".to_string(),
}); });
if let Some(viewport) = cmd.get("viewport") { if let Some(viewport) = cmd.get("viewport") {
+9 -1
View File
@@ -117,6 +117,7 @@ pub struct PageInfo {
pub session_id: String, pub session_id: String,
pub url: String, pub url: String,
pub title: String, pub title: String,
pub target_type: String, // "page" or "webview"
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@@ -315,7 +316,9 @@ impl BrowserManager {
let page_targets: Vec<TargetInfo> = result let page_targets: Vec<TargetInfo> = result
.target_infos .target_infos
.into_iter() .into_iter()
.filter(|t| t.target_type == "page" && !t.url.is_empty()) .filter(|t| {
(t.target_type == "page" || t.target_type == "webview") && !t.url.is_empty()
})
.collect(); .collect();
if page_targets.is_empty() { if page_targets.is_empty() {
@@ -348,6 +351,7 @@ impl BrowserManager {
session_id: attach_result.session_id.clone(), session_id: attach_result.session_id.clone(),
url: "about:blank".to_string(), url: "about:blank".to_string(),
title: String::new(), title: String::new(),
target_type: "page".to_string(),
}); });
self.active_page_index = 0; self.active_page_index = 0;
self.enable_domains(&attach_result.session_id).await?; self.enable_domains(&attach_result.session_id).await?;
@@ -370,6 +374,7 @@ impl BrowserManager {
session_id: attach_result.session_id.clone(), session_id: attach_result.session_id.clone(),
url: target.url.clone(), url: target.url.clone(),
title: target.title.clone(), title: target.title.clone(),
target_type: target.target_type.clone(),
}); });
} }
@@ -664,6 +669,7 @@ impl BrowserManager {
session_id: attach_result.session_id.clone(), session_id: attach_result.session_id.clone(),
url: "about:blank".to_string(), url: "about:blank".to_string(),
title: String::new(), title: String::new(),
target_type: "page".to_string(),
}); });
self.active_page_index = 0; self.active_page_index = 0;
self.enable_domains(&attach_result.session_id).await?; self.enable_domains(&attach_result.session_id).await?;
@@ -696,6 +702,7 @@ impl BrowserManager {
"index": i, "index": i,
"title": p.title, "title": p.title,
"url": p.url, "url": p.url,
"type": p.target_type,
"active": i == self.active_page_index, "active": i == self.active_page_index,
}) })
}) })
@@ -736,6 +743,7 @@ impl BrowserManager {
session_id: attach.session_id, session_id: attach.session_id,
url: target_url.to_string(), url: target_url.to_string(),
title: String::new(), title: String::new(),
target_type: "page".to_string(),
}); });
self.active_page_index = index; self.active_page_index = index;
+25
View File
@@ -102,6 +102,31 @@ agent-browser tab 2
agent-browser tab --url "*settings*" agent-browser tab --url "*settings*"
``` ```
## Webview Support
Electron `<webview>` elements are automatically discovered and can be controlled like regular pages. When using `--native` mode, webviews appear as separate targets in the tab list with `type: "webview"`:
```bash
# Connect in native mode
agent-browser --native connect 9222
# List targets -- webviews appear alongside pages
agent-browser tab
# Example output:
# 0: [page] Slack - Main Window https://app.slack.com/
# 1: [webview] Embedded Content https://example.com/widget
# Switch to a webview
agent-browser tab 1
# Interact with the webview normally
agent-browser snapshot -i
agent-browser click @e3
agent-browser screenshot webview.png
```
**Note:** Webview support requires `--native` mode (raw CDP). The Playwright-based mode does not support webview targets.
## Common Patterns ## Common Patterns
### Inspect and Navigate an App ### Inspect and Navigate an App