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, ¶ms_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}