Skip to main content

bao_browser/
cdp_memory.rs

1//! Production `InMemoryBridge` — the memory:// CDP transport's host side.
2//!
3//! `Browser::connect("memory://bao")` (eager form, via the client's
4//! process-global registry) dispatches every CDP command here. This bridge
5//! routes through the REAL protocol dispatcher (`bao_cdp::handle_command`)
6//! with a REAL `BridgeSender`, so:
7//!
8//! - Pure-protocol domains (`Browser.getVersion`, …) answer instantly.
9//! - Servo-touching commands (`Runtime.evaluate`, `Target.getTargets`
10//!   listing, …) ride the bridge channel to whoever drains it — the
11//!   runtime's event loop (`BaoRuntime::run`) drains it on its own thread.
12//!   When nothing drains (a bare `BaoRuntime::new` consumer that never
13//!   pumps), those commands fail FAST with an honest timeout error (the
14//!   channel is created with a short timeout) instead of returning
15//!   fabricated results — the bridge-less protocol fallback fabricates
16//!   `undefined` for `Runtime.evaluate`, which is exactly the silent-fake
17//!   class this workspace eradicates.
18//!
19//! @trace REQ-CDP-001 [level:library]
20
21use std::sync::Arc;
22
23use bao_cdp::servo_bridge::{bridge_channel, BridgeSender};
24use bao_cdp::{handle_command, CdpMessage};
25use bao_cdp_client::transport::in_memory::{InMemoryBridge, InMemoryBridgeResponse};
26
27/// How long an undrained bridge command waits before failing. Short by
28/// design: the documented no-pump consumer shape (`connect` → `version()` /
29/// `pages()`) must not hang; servo-routed commands degrade to honest
30/// errors, and `run()`-driven consumers get full fidelity.
31const UNDRAINED_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(2);
32
33/// The host-side bridge installed into `bao_cdp_client`'s process registry
34/// by [`crate::BaoRuntime::new`].
35pub struct MemoryCdpBridge {
36    sender: BridgeSender,
37    /// Target used when the client sends no sessionId (flat/single-target
38    /// memory clients — the `memory://bao` shape has no discovery step).
39    /// Tracks the runtime's most recently created page so flat clients
40    /// (no Target.attachTarget dance) land on a live page.
41    default_target: std::sync::Mutex<String>,
42}
43
44impl MemoryCdpBridge {
45    /// Create the bridge pair: the sender side for the client registry, and
46    /// the receiver the runtime must drain (`BaoRuntime::run` does).
47    pub fn new(default_target: impl Into<String>) -> (Arc<Self>, bao_cdp::servo_bridge::BridgeReceiver) {
48        let (sender, receiver) = bridge_channel(UNDRAINED_TIMEOUT);
49        (
50            Arc::new(Self {
51                sender,
52                default_target: std::sync::Mutex::new(default_target.into()),
53            }),
54            receiver,
55        )
56    }
57
58    /// Point the flat (sessionId-less) client face at a live page. Called by
59    /// [`crate::BaoRuntime::create_page`] so `memory://` clients without an
60    /// explicit target route to the newest page.
61    pub fn set_default_target(&self, target: impl Into<String>) {
62        *self.default_target.lock().unwrap() = target.into();
63    }
64}
65
66impl InMemoryBridge for MemoryCdpBridge {
67    fn dispatch_command(
68        &self,
69        method: &str,
70        params: serde_json::Value,
71        session_id: Option<&str>,
72    ) -> InMemoryBridgeResponse {
73        let owned_default = self.default_target.lock().unwrap().clone();
74        let target = session_id.unwrap_or(&owned_default);
75        let msg = CdpMessage {
76            id: Some(0),
77            method: method.to_string(),
78            params: Some(params),
79            session_id: None,
80        };
81        let params_ref = msg.params.clone();
82        let response = handle_command(msg, target, &params_ref, Some(&self.sender));
83        match response.error {
84            Some(err) => InMemoryBridgeResponse::Err(err.message),
85            None => InMemoryBridgeResponse::Ok(response.result.unwrap_or(serde_json::Value::Null)),
86        }
87    }
88}