agentmux_cef\reducer/
browsers.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Browser handle registry (Phase H.2) reducer handlers. Extracted from reducer/mod.rs in
5//! task #182 PR-F-2 for navigability.
6
7use std::time::Instant;
8
9use cef::Browser;
10
11use crate::state::*;
12
13use super::{DispatchOutput, HostEvent, HostLifecyclePhase, HostState, emit_error};
14
15// ── H.2 — browser handle registry ────────────────────────────────────────
16
17pub(super) fn handle_register_browser(
18    state: &mut HostState,
19    label: String,
20    browser: Browser,
21    kind: BrowserKind,
22) -> DispatchOutput {
23    if state.lifecycle == HostLifecyclePhase::ShuttingDown {
24        return emit_error(state, format!("register_browser: shutting down (label={})", label));
25    }
26    if state.browsers.contains_key(&label) {
27        return emit_error(state, format!("register_browser: label {} already registered", label));
28    }
29    state.browsers.insert(
30        label.clone(),
31        BrowserHandle {
32            label: label.clone(),
33            browser,
34            kind: kind.clone(),
35            registered_at: Instant::now(),
36        },
37    );
38    let v = state.bump_version();
39    DispatchOutput {
40        events: vec![HostEvent::BrowserRegistered { label, kind, version: v }],
41        ..Default::default()
42    }
43}
44
45pub(super) fn handle_unregister_browser(state: &mut HostState, label: String) -> DispatchOutput {
46    // Atomic remove + return the Browser handle in `removed_browser`
47    // (codex P2 PR #660). The pane close path in
48    // `browser_panes::AppStateCloseOps::take_browser_hwnd` uses the
49    // returned Browser to extract its HWND. Any caller that doesn't
50    // need the handle can simply ignore `removed_browser`.
51    let removed = state.browsers.remove(&label);
52    let removed_browser = removed.map(|h| h.browser);
53    if removed_browser.is_none() {
54        return DispatchOutput::default(); // idempotent
55    }
56    let v = state.bump_version();
57    DispatchOutput {
58        events: vec![HostEvent::BrowserUnregistered { label, version: v }],
59        removed_browser,
60        ..Default::default()
61    }
62}
63