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}