agentmux_cef\browser_pane/creation.rs
1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! UI-thread task that actually creates a CEF browser pane.
5//!
6//! Moved out of `browser_panes.rs` during Phase 3 of the pane modularization
7//! split (see `docs/specs/SPEC_BROWSER_PANE_MODULARIZATION.md` §6). The task
8//! structure here is a straight lift — same pre-flight checks, same
9//! `browser_host_create_browser` call. `BrowserPaneManager::create` still
10//! calls `post_task(ThreadId::UI, ..)` with an instance of this task; the
11//! only change is the import path.
12//!
13//! Dependencies (one-way, no cycle):
14//! - `cef::*` for CEF types and the `wrap_task!` macro.
15//! - `crate::state::AppState` for the label queue and the Arc passed to
16//! the pane's handler.
17//! - `crate::client::{AgentMuxHandler, AgentMuxClient}` for the pane's
18//! CEF client. Phase 4 will flip this direction by moving the pane
19//! callbacks into `pane/callbacks.rs`; until then, `client` is fine as
20//! a one-way dependency.
21//! - `crate::commands::window::find_own_top_level_window` for the parent
22//! HWND (CEF Views returns null on Alloy).
23
24use std::sync::Arc;
25
26use cef::*;
27
28use crate::state::AppState;
29
30wrap_task! {
31 pub struct CreateBrowserPaneTask {
32 state: Arc<AppState>,
33 block_id: String,
34 label: String,
35 url: String,
36 rect: Rect,
37 // Linux/macOS only: which top-level CefWindow to attach the pane
38 // overlay to (looked up in `state.windows`). Windows path doesn't
39 // read this field — `find_own_top_level_window` resolves the
40 // calling window's HWND directly.
41 window_label: String,
42 }
43
44 impl Task {
45 fn execute(&self) {
46 // Running on the CEF UI thread.
47 //
48 // Two completely separate paths by platform:
49 // - Windows: native child window via WindowInfo::set_as_child +
50 // browser_host_create_browser. Requires the parent HWND from
51 // find_own_top_level_window.
52 // - Linux / macOS: CEF Views via browser_view_create +
53 // Window::add_overlay_view. The Windows native-child path does
54 // not work on Wayland (cef#2804) and is officially unsupported
55 // on macOS. We use AddOverlayView (not AddChildView) so the
56 // pane cohabits cleanly with the host UI's full-window
57 // BrowserView. See
58 // `docs/specs/embedded-browser-panes-linux-macos-2026-05-03.md`.
59
60 #[cfg(not(target_os = "windows"))]
61 {
62 crate::browser_pane::creation_views::create_browser_pane_view(
63 self.state.clone(),
64 self.block_id.clone(),
65 self.label.clone(),
66 self.url.clone(),
67 self.rect.clone(),
68 self.window_label.clone(),
69 );
70 return;
71 }
72
73 #[cfg(target_os = "windows")]
74 {
75 // Get parent HWND via Win32 enumeration (CEF Views returns null).
76 let parent_hwnd_raw = unsafe {
77 crate::commands::window::find_own_top_level_window()
78 };
79 if parent_hwnd_raw.is_null() {
80 tracing::error!(block_id = %self.block_id, "cannot find main window HWND — aborting browser pane creation");
81 return;
82 }
83
84 // Phase B.5 (window_meta step d) — pre-create handoff.
85 // Browser panes are not top-level windows; the kind value here
86 // is irrelevant (on_after_created skips the taskbar/report-open
87 // logic for browser-pane-* labels).
88 // Phase F.1 — routed through the host reducer.
89 self.state.host_dispatch(
90 crate::reducer::HostCommand::EnqueuePendingWindowCreation {
91 entry: crate::state::PendingWindowCreation {
92 label: self.label.clone(),
93 kind: crate::state::WindowKind::FullInstance,
94 parent_instance_id: None,
95 },
96 },
97 );
98
99 let handler = crate::client::AgentMuxHandler::new_with_browser_pane(self.state.clone(), 0, true);
100 let mut client = Some(crate::client::AgentMuxClient::new(handler, true));
101
102 let url_cef = CefString::from(self.url.as_str());
103 let settings = BrowserSettings::default();
104
105 let parent_hwnd = sys::HWND(parent_hwnd_raw as *mut _);
106 // Use the clean set_as_child helper — it fills style/parent/bounds
107 // correctly and leaves other fields zeroed (in particular `window`
108 // which is an OUTPUT field filled by CEF).
109 let mut window_info = WindowInfo::default().set_as_child(parent_hwnd, &self.rect);
110 // Match the main process runtime style (ALLOY throughout the app).
111 window_info.runtime_style = RuntimeStyle::ALLOY;
112
113 let result = browser_host_create_browser(
114 Some(&window_info),
115 client.as_mut(),
116 Some(&url_cef),
117 Some(&settings),
118 None, // extra_info
119 None, // request_context
120 );
121
122 if result == 0 {
123 tracing::error!(block_id = %self.block_id, "browser_host_create_browser returned 0");
124 return;
125 }
126
127 tracing::info!(
128 block_id = %self.block_id,
129 label = %self.label,
130 url = %self.url,
131 x = self.rect.x, y = self.rect.y,
132 w = self.rect.width, h = self.rect.height,
133 "browser pane created on UI thread"
134 );
135 }
136 }
137 }
138}