diff --git a/moli-protocol-server/src/cdp_frontend.rs b/moli-protocol-server/src/cdp_frontend.rs index 2bb312ad66..2b9657e4aa 100644 --- a/moli-protocol-server/src/cdp_frontend.rs +++ b/moli-protocol-server/src/cdp_frontend.rs @@ -2,7 +2,6 @@ use anyhow::{Context, Result, bail}; use moli_protocol_cdp::{CdpCommandParseError, ParsedCdpCommand}; use tokio::sync::{mpsc, oneshot}; -use crate::cdp_frontend_router::{CdpFrontendRouter, CdpFrontendSessionLookup}; use crate::cdp_writer::CdpSocketSink; const FRONTEND_COMMAND_QUEUE_CAPACITY: usize = 256; @@ -17,7 +16,6 @@ pub(crate) struct CdpFrontendEndpoint { control_tx: mpsc::UnboundedSender, command_tx: mpsc::Sender, shutdown_tx: tokio::sync::watch::Sender, - session_lookup: CdpFrontendSessionLookup, } pub(crate) enum CdpFrontendControlRequest { @@ -68,24 +66,20 @@ pub(crate) struct CdpCreatedTarget { pub(crate) tab_target_id: String, } -pub(crate) fn cdp_frontend_channel() --> (CdpFrontendEndpoint, CdpFrontendReceivers, CdpFrontendRouter) { +pub(crate) fn cdp_frontend_channel() -> (CdpFrontendEndpoint, CdpFrontendReceivers) { let (control_tx, control_rx) = mpsc::unbounded_channel(); let (command_tx, command_rx) = mpsc::channel(FRONTEND_COMMAND_QUEUE_CAPACITY); let (shutdown_tx, _) = tokio::sync::watch::channel(false); - let router = CdpFrontendRouter::new(); ( CdpFrontendEndpoint { control_tx, command_tx, shutdown_tx, - session_lookup: router.session_lookup(), }, CdpFrontendReceivers { control_rx, command_rx, }, - router, ) } @@ -133,10 +127,6 @@ impl CdpFrontendEndpoint { } } - pub(crate) fn owns_session(&self, frontend_id: u64, session_id: Option<&str>) -> bool { - self.session_lookup.owns_session(frontend_id, session_id) - } - pub(crate) async fn command( &self, frontend_id: u64, diff --git a/moli-protocol-server/src/cdp_frontend_router.rs b/moli-protocol-server/src/cdp_frontend_router.rs index 90cafbb122..9196ef3282 100644 --- a/moli-protocol-server/src/cdp_frontend_router.rs +++ b/moli-protocol-server/src/cdp_frontend_router.rs @@ -1,7 +1,4 @@ -use std::{ - sync::{Arc, Weak}, - time::Instant, -}; +use std::time::Instant; use anyhow::Result; use moli_protocol::ParsedCdpCommand; @@ -21,32 +18,13 @@ pub(crate) enum CdpPreparedFrontendCommand { } pub(crate) struct CdpFrontendRouter { - routing: Arc>, -} - -#[derive(Clone)] -pub(crate) struct CdpFrontendSessionLookup { - routing: Weak>, -} - -impl CdpFrontendSessionLookup { - pub(crate) fn owns_session(&self, frontend_id: u64, session_id: Option<&str>) -> bool { - self.routing - .upgrade() - .is_some_and(|routing| routing.lock().owns_session(frontend_id, session_id)) - } + routing: Mutex, } impl CdpFrontendRouter { pub(crate) fn new() -> Self { Self { - routing: Arc::new(Mutex::new(CdpFrontendRoutingState::default())), - } - } - - pub(crate) fn session_lookup(&self) -> CdpFrontendSessionLookup { - CdpFrontendSessionLookup { - routing: Arc::downgrade(&self.routing), + routing: Mutex::new(CdpFrontendRoutingState::default()), } } @@ -67,6 +45,14 @@ impl CdpFrontendRouter { } } + /// Route a response through the ordinary command table, returning its + /// writer when the caller needs a flush confirmation before a side effect. + pub(crate) fn enqueue_response(&self, message: Value) -> Option { + let (frontend, message) = self.routing.lock().route_message(message, None)?; + let sink = frontend.into_sink(); + sink.enqueue_owned_message(message).then_some(sink) + } + pub(crate) fn register_browser_frontend( &self, frontend_id: u64, diff --git a/moli-protocol-server/src/cdp_frontend_router/routing.rs b/moli-protocol-server/src/cdp_frontend_router/routing.rs index 41af96a288..32f6dc4d87 100644 --- a/moli-protocol-server/src/cdp_frontend_router/routing.rs +++ b/moli-protocol-server/src/cdp_frontend_router/routing.rs @@ -23,6 +23,10 @@ impl CdpRoutedFrontend { pub(super) fn enqueue_message(self, message: Value) -> bool { self.sink.enqueue_owned_message(message) } + + pub(super) fn into_sink(self) -> CdpSocketSink { + self.sink + } } #[derive(Default)] @@ -34,13 +38,6 @@ pub(super) struct CdpFrontendRoutingState { } impl CdpFrontendRoutingState { - pub(super) fn owns_session(&self, frontend_id: u64, session_id: Option<&str>) -> bool { - match session_id { - Some(session_id) => self.frontends.owns_client_session(frontend_id, session_id), - None => self.frontends.base_session_id(frontend_id).is_some(), - } - } - pub(super) fn register_browser_frontend( &mut self, frontend_id: u64, diff --git a/moli-protocol-server/src/cdp_frontend_router/routing/tests.rs b/moli-protocol-server/src/cdp_frontend_router/routing/tests.rs index 5ea1cf7d36..c09bb9fa0b 100644 --- a/moli-protocol-server/src/cdp_frontend_router/routing/tests.rs +++ b/moli-protocol-server/src/cdp_frontend_router/routing/tests.rs @@ -26,6 +26,51 @@ fn expect_prepared_command( } } +#[test] +fn instance_command_responses_use_normal_command_and_session_routes_once() { + let router = CdpFrontendRouter::new(); + let (sink, mut writer) = CdpSocketSink::with_stalled_writer_for_test(4); + router + .register_browser_frontend(5, "SID-browser".into(), sink) + .unwrap(); + assert!( + router.enqueue_protocol_output_sequence(ProtocolOutputSequence::from_messages(vec![ + json!({ + "method": "Target.attachedToTarget", + "sessionId": "SID-browser", + "params": { + "sessionId": "SID-child", + "targetInfo": {"targetId": "TID-child"}, + }, + }), + ])) + ); + assert_eq!(writer.take_message()["method"], "Target.attachedToTarget"); + + for session_id in [None, Some("SID-child")] { + let mut request = json!({"id": 42, "method": "Browser.close"}); + if let Some(session_id) = session_id { + request["sessionId"] = json!(session_id); + } + let command = expect_prepared_command( + router.prepare_command(5, ParsedCdpCommand::parse_value(request)), + "instance close", + ); + assert_ne!(command.request().id(), 42); + let response = json!({"id": command.request().id(), "result": {}}); + assert!(router.enqueue_response(response.clone()).is_some()); + let mut expected = json!({"id": 42, "result": {}}); + if let Some(session_id) = session_id { + expected["sessionId"] = json!(session_id); + } + assert_eq!(writer.take_message(), expected); + assert!( + router.enqueue_response(response).is_none(), + "responding consumes the ordinary pending-command route" + ); + } +} + #[test] fn browser_and_page_client_command_ids_are_isolated_and_restored() { let mut routing = CdpFrontendRoutingState::default(); diff --git a/moli-protocol-server/src/cdp_scheduler.rs b/moli-protocol-server/src/cdp_scheduler.rs index 7277895b28..750912e448 100644 --- a/moli-protocol-server/src/cdp_scheduler.rs +++ b/moli-protocol-server/src/cdp_scheduler.rs @@ -36,6 +36,7 @@ mod actor; mod adapter_scheduler; mod command_dispatch; mod frontend_control; +mod instance_command; mod protocol_residence; mod runtime_command_barrier; mod runtime_dispatch; diff --git a/moli-protocol-server/src/cdp_scheduler/actor.rs b/moli-protocol-server/src/cdp_scheduler/actor.rs index 67c30124aa..06924bd26a 100644 --- a/moli-protocol-server/src/cdp_scheduler/actor.rs +++ b/moli-protocol-server/src/cdp_scheduler/actor.rs @@ -377,6 +377,7 @@ async fn run_cdp_scheduler_actor( &mut in_flight_commands, &mut blocked_commands, &mut next_in_flight_command_token, + owner_lifecycle.as_ref(), ) .await { @@ -1408,6 +1409,7 @@ async fn handle_frontend_command( in_flight_commands: &mut InFlightCommands, blocked_commands: &mut VecDeque, next_in_flight_command_token: &mut u64, + owner_lifecycle: Option<&CdpOwnerActorLifecycle>, ) -> bool { let CdpFrontendCommand { frontend_id, @@ -1427,6 +1429,16 @@ async fn handle_frontend_command( } }; trace_command(&command, "frontend_command_received", None, None, None); + if let Some(owner_lifecycle) = owner_lifecycle + && super::instance_command::handle_instance_command( + &command, + frontend_router, + &owner_lifecycle.instance_control, + ) + .await + { + return true; + } handle_client_command_with_interleaved_output( frontend_router, scheduler, diff --git a/moli-protocol-server/src/cdp_scheduler/frontend_control.rs b/moli-protocol-server/src/cdp_scheduler/frontend_control.rs index e9e8a7f62b..24be09aab4 100644 --- a/moli-protocol-server/src/cdp_scheduler/frontend_control.rs +++ b/moli-protocol-server/src/cdp_scheduler/frontend_control.rs @@ -1,7 +1,10 @@ use moli_cookie_jar::StoredCookie; use tokio::sync::mpsc; -use crate::{cdp_frontend::CdpFrontendControlRequest, cdp_frontend_router::CdpFrontendRouter}; +use crate::{ + BrowserInstanceControl, cdp_frontend::CdpFrontendControlRequest, + cdp_frontend_router::CdpFrontendRouter, +}; use self::target_control::CdpFrontendTargetControl; use super::CdpScheduler; @@ -27,6 +30,7 @@ impl CdpCookieSnapshot { pub(crate) struct CdpOwnerActorLifecycle { pub(crate) checkpoint_tx: mpsc::UnboundedSender, + pub(crate) instance_control: BrowserInstanceControl, } #[derive(Default)] diff --git a/moli-protocol-server/src/cdp_scheduler/instance_command.rs b/moli-protocol-server/src/cdp_scheduler/instance_command.rs new file mode 100644 index 0000000000..bc87ec310a --- /dev/null +++ b/moli-protocol-server/src/cdp_scheduler/instance_command.rs @@ -0,0 +1,78 @@ +//! CDP instance commands run after normal frontend routing and before page work. + +use std::time::Duration; + +use moli_protocol::ParsedCdpCommand; +use serde_json::json; + +use crate::{BrowserInstanceControl, InstanceCommand, cdp_frontend_router::CdpFrontendRouter}; + +const BROWSER_CLOSE_FLUSH_BUDGET: Duration = Duration::from_secs(1); + +/// The actor has already validated the request's session through the Router. +/// Its internal command ID routes the response back to the original frontend, +/// client command ID, and public session using the ordinary response path. +pub(super) async fn handle_instance_command( + command: &ParsedCdpCommand, + router: &CdpFrontendRouter, + control: &BrowserInstanceControl, +) -> bool { + let instance_command = match command.method() { + "Browser.close" => InstanceCommand::Shutdown, + _ => return false, + }; + let Some(sink) = router.enqueue_response(json!({"id": command.request().id(), "result": {}})) + else { + return true; + }; + if matches!( + tokio::time::timeout(BROWSER_CLOSE_FLUSH_BUDGET, sink.flush()).await, + Ok(true) + ) { + control.request(instance_command); + } else { + // A failed acknowledgment retires this frontend, not the instance or + // the actor serving other connections. + sink.close(); + } + true +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{cdp_frontend_router::CdpPreparedFrontendCommand, cdp_writer::CdpSocketSink}; + + #[tokio::test] + async fn failed_or_unconfirmed_flush_does_not_stop_the_instance() { + let control = BrowserInstanceControl::new(); + let router = CdpFrontendRouter::new(); + let (stalled_sink, stalled_writer) = CdpSocketSink::with_stalled_writer_for_test(2); + router + .register_browser_frontend(1, "hidden-dead".into(), CdpSocketSink::for_test()) + .unwrap(); + router + .register_browser_frontend(2, "hidden-stalled".into(), stalled_sink) + .unwrap(); + for frontend_id in [1, 2] { + let parsed = ParsedCdpCommand::parse_value(json!({ + "id": 42, "method": "Browser.close", + })); + let Some(CdpPreparedFrontendCommand::Command(command)) = + router.prepare_command(frontend_id, parsed) + else { + panic!("registered frontend must route normally"); + }; + assert!(handle_instance_command(&command, &router, &control).await); + } + assert!( + !stalled_writer.is_open(), + "failed flush closes its frontend" + ); + assert!( + tokio::time::timeout(Duration::from_millis(10), control.shutdown_requested()) + .await + .is_err() + ); + } +} diff --git a/moli-protocol-server/src/protocol_server/cdp_instance_control.rs b/moli-protocol-server/src/protocol_server/cdp_instance_control.rs deleted file mode 100644 index 3ffdbcd6f3..0000000000 --- a/moli-protocol-server/src/protocol_server/cdp_instance_control.rs +++ /dev/null @@ -1,156 +0,0 @@ -//! Translate CDP instance commands without entering a page's work queue. - -use std::time::Duration; - -use moli_protocol::ParsedCdpCommand; -use serde_json::{Value, json}; - -use super::{BrowserInstanceControl, InstanceCommand}; -use crate::{cdp_frontend::CdpFrontendEndpoint, cdp_writer::CdpSocketSink}; - -const BROWSER_CLOSE_FLUSH_BUDGET: Duration = Duration::from_secs(1); - -/// Return true when this adapter consumed a command and the frontend should -/// close, including when its acknowledgment could not be flushed. -pub(super) async fn handle_instance_command( - command: &ParsedCdpCommand, - endpoint: &CdpFrontendEndpoint, - frontend_id: u64, - sink: &CdpSocketSink, - control: &BrowserInstanceControl, -) -> bool { - let Some((command, response)) = instance_command(command, endpoint, frontend_id) else { - return false; - }; - acknowledge_instance_command(sink, control, command, response).await; - true -} - -// Instance control bypasses page work, using the normal wire parser and the -// owner's session registry. Chromium permits it on page and attached routes too. -fn instance_command( - command: &ParsedCdpCommand, - endpoint: &CdpFrontendEndpoint, - frontend_id: u64, -) -> Option<(InstanceCommand, Value)> { - let request = command.request(); - if request.method() != "Browser.close" - || !endpoint.owns_session(frontend_id, request.session_id()) - { - return None; - } - let mut response = json!({"id": request.id(), "result": {}}); - if let Some(session_id) = request.session_id() { - response["sessionId"] = json!(session_id); - } - Some((InstanceCommand::Shutdown, response)) -} - -async fn acknowledge_instance_command( - sink: &CdpSocketSink, - control: &BrowserInstanceControl, - command: InstanceCommand, - response: Value, -) -> bool { - if !sink.enqueue_owned_message(response) { - return false; - } - if !matches!( - tokio::time::timeout(BROWSER_CLOSE_FLUSH_BUDGET, sink.flush()).await, - Ok(true) - ) { - return false; - } - control.request(command); - true -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn only_a_valid_owned_frontend_close_can_request_instance_shutdown() { - let (endpoint, _receivers, router) = crate::cdp_frontend::cdp_frontend_channel(); - router - .register_browser_frontend(1, "hidden-browser".into(), CdpSocketSink::for_test()) - .unwrap(); - router - .register_target_frontend( - 2, - "page".into(), - "hidden-page".into(), - CdpSocketSink::for_test(), - ) - .unwrap(); - for value in [ - json!({"id": 7, "method": "Browser.close"}), - json!({"id": 7, "method": "Browser.close", "params": {}}), - json!({"id": 7, "method": "Browser.close", "params": {"ignored": true}}), - json!({"id": 7, "method": "Browser.close", "params": []}), - json!({"id": 7, "method": "Browser.close", "params": null}), - ] { - let command = ParsedCdpCommand::parse_value(value).unwrap(); - for frontend_id in [1, 2] { - assert_eq!( - instance_command(&command, &endpoint, frontend_id), - Some((InstanceCommand::Shutdown, json!({"id": 7, "result": {}}))) - ); - } - } - for value in [ - json!({"id": 7, "method": "Browser.close", "sessionId": "unknown"}), - json!({"id": 7, "method": "Browser.close", "sessionId": "hidden-browser"}), - json!({"id": 7, "method": "browser.close"}), - ] { - let command = ParsedCdpCommand::parse_value(value).unwrap(); - assert!(instance_command(&command, &endpoint, 1).is_none()); - } - for value in [ - json!({"id": 7, "method": "Browser.close", "sessionId": null}), - json!({"id": -1, "method": "Browser.close"}), - json!({"id": "7", "method": "Browser.close"}), - json!({"method": "Browser.close"}), - ] { - assert!(ParsedCdpCommand::parse_value(value).is_err()); - } - drop(router); - let command = - ParsedCdpCommand::parse_value(json!({"id": 7, "method": "Browser.close"})).unwrap(); - assert!( - instance_command(&command, &endpoint, 1).is_none(), - "a transport must not keep its departed owner alive" - ); - } - - #[tokio::test] - async fn failed_or_unconfirmed_flush_does_not_stop_the_instance() { - let control = BrowserInstanceControl::new(); - let sink = CdpSocketSink::for_test(); - assert!( - !acknowledge_instance_command( - &sink, - &control, - InstanceCommand::Shutdown, - json!({"id": 1, "result": {}}) - ) - .await - ); - let (sink, _stalled_writer) = CdpSocketSink::with_stalled_writer_for_test(2); - assert!( - !acknowledge_instance_command( - &sink, - &control, - InstanceCommand::Shutdown, - json!({"id": 2, "result": {}}) - ) - .await - ); - assert!( - tokio::time::timeout(Duration::from_millis(10), control.shutdown_requested()) - .await - .is_err() - ); - assert!(control.connection().is_some()); - } -} diff --git a/moli-protocol-server/src/protocol_server/cdp_owner.rs b/moli-protocol-server/src/protocol_server/cdp_owner.rs index 4c877f9b86..73b2972f72 100644 --- a/moli-protocol-server/src/protocol_server/cdp_owner.rs +++ b/moli-protocol-server/src/protocol_server/cdp_owner.rs @@ -19,7 +19,8 @@ use crate::{ }; use super::{ - CookieProfileCommit, SharedCookieProfile, cdp_agent_host::SharedCdpAgentHostDirectory, + BrowserInstanceControl, CookieProfileCommit, SharedCookieProfile, + cdp_agent_host::SharedCdpAgentHostDirectory, protocol_local_executor::spawn_protocol_local_task, }; @@ -58,9 +59,11 @@ struct CdpOwnerRuntimeConfig { storage_partition: Arc, navigation_runtime_config: NavigationRuntimeConfig, screencast_interval_ms: u32, + instance_control: BrowserInstanceControl, } impl SharedCdpOwnerRegistry { + #[allow(clippy::too_many_arguments)] pub(super) fn new( directory: SharedCdpAgentHostDirectory, target_id_allocator: Arc, @@ -69,6 +72,7 @@ impl SharedCdpOwnerRegistry { storage_partition: Arc, navigation_runtime_config: NavigationRuntimeConfig, screencast_interval_ms: u32, + instance_control: BrowserInstanceControl, ) -> Self { Self { inner: Arc::new(CdpOwnerRegistryInner { @@ -80,6 +84,7 @@ impl SharedCdpOwnerRegistry { storage_partition, navigation_runtime_config, screencast_interval_ms, + instance_control, }, state: Mutex::new(CdpOwnerRegistryState::default()), shared_owner: Mutex::new(None), @@ -102,7 +107,7 @@ impl SharedCdpOwnerRegistry { fn spawn_owner(&self) -> Result<(u64, CdpFrontendEndpoint)> { let owner_id = self.inner.config.directory.allocate_owner_id(); - let (endpoint, receivers, frontend_router) = cdp_frontend_channel(); + let (endpoint, receivers) = cdp_frontend_channel(); let lifecycle_observer = self .inner .config @@ -134,7 +139,6 @@ impl SharedCdpOwnerRegistry { ); let owner_finished_rx = spawn_owner_task( receivers, - frontend_router, initial_storage_partition, self.inner.config.navigation_runtime_config.clone(), self.inner.config.screencast_interval_ms, @@ -145,6 +149,7 @@ impl SharedCdpOwnerRegistry { )), Some(CdpOwnerActorLifecycle { checkpoint_tx: checkpoint_tx.clone(), + instance_control: self.inner.config.instance_control.clone(), }), checkpoint_tx, ); @@ -203,7 +208,6 @@ impl Drop for CdpOwnerRegistryInner { #[allow(clippy::too_many_arguments)] fn spawn_owner_task( receivers: CdpFrontendReceivers, - frontend_router: CdpFrontendRouter, initial_storage_partition: CdpInitialStoragePartition, navigation_runtime_config: NavigationRuntimeConfig, screencast_interval_ms: u32, @@ -222,7 +226,7 @@ fn spawn_owner_task( let actor = spawn_cdp_scheduler_actor( scheduler, scheduler_receivers, - frontend_router, + CdpFrontendRouter::new(), receivers, owner_lifecycle, ); diff --git a/moli-protocol-server/src/protocol_server/cdp_socket.rs b/moli-protocol-server/src/protocol_server/cdp_socket.rs index 1de63252f8..5528fdce78 100644 --- a/moli-protocol-server/src/protocol_server/cdp_socket.rs +++ b/moli-protocol-server/src/protocol_server/cdp_socket.rs @@ -3,7 +3,7 @@ use futures_util::StreamExt; use moli_protocol::ParsedCdpCommand; use std::time::Duration; -use super::{BrowserInstanceControl, cdp_instance_control::handle_instance_command}; +use super::BrowserInstanceControl; use crate::{cdp_frontend::CdpFrontendEndpoint, cdp_writer::spawn_socket_sink}; pub(super) enum CdpFrontendSocketKind { @@ -57,13 +57,6 @@ pub(super) async fn run_cdp_frontend_socket( match message { Message::Text(text) => { let command = ParsedCdpCommand::parse_str(text.to_string()); - if let Ok(command) = &command - && handle_instance_command( - command, &endpoint, frontend_id, &socket_sink, &control, - ).await - { - break CdpSocketExit::TransportClose; - } if !endpoint.command(frontend_id, command).await { break CdpSocketExit::TransportClose; } diff --git a/moli-protocol-server/src/protocol_server/mod.rs b/moli-protocol-server/src/protocol_server/mod.rs index bb9cc303a0..af664c0b29 100644 --- a/moli-protocol-server/src/protocol_server/mod.rs +++ b/moli-protocol-server/src/protocol_server/mod.rs @@ -41,7 +41,6 @@ pub use instance_control::{BrowserInstanceControl, InstanceCommand}; mod cdp; mod cdp_agent_host; -mod cdp_instance_control; mod cdp_owner; mod cdp_socket; mod instance_control; @@ -164,13 +163,13 @@ impl ProtocolServer { info!(%addr, "protocol server listening"); - let mut app_state = AppState::new_with_storage_partition_and_runtime_config( + let app_state = AppState::new_with_storage_partition_and_runtime_config( addr, self.storage_partition.clone(), self.navigation_runtime_config.clone(), self.config.screencast_interval_ms, + control, )?; - app_state.control = control; serve_listener(listener, app_state).await } @@ -830,6 +829,7 @@ impl AppState { LayoutPolicy::default(), ), crate::config::DEFAULT_SCREENCAST_INTERVAL_MS, + BrowserInstanceControl::new(), ) } @@ -838,6 +838,7 @@ impl AppState { storage_partition: Arc, navigation_runtime_config: NavigationRuntimeConfig, screencast_interval_ms: u32, + control: BrowserInstanceControl, ) -> anyhow::Result { Ok(Self::from_parts( addr, @@ -845,6 +846,7 @@ impl AppState { storage_partition, navigation_runtime_config, screencast_interval_ms, + control, )) } @@ -854,6 +856,7 @@ impl AppState { storage_partition: Arc, navigation_runtime_config: NavigationRuntimeConfig, screencast_interval_ms: u32, + control: BrowserInstanceControl, ) -> Self { let cdp_agent_host_directory = SharedCdpAgentHostDirectory::default(); let cdp_target_id_allocator = Arc::new(AtomicU64::new(0)); @@ -866,9 +869,10 @@ impl AppState { storage_partition.clone(), navigation_runtime_config.clone(), screencast_interval_ms, + control.clone(), ); Self { - control: BrowserInstanceControl::new(), + control, browser_ws_url: format!("ws://{addr}/devtools/browser/{DEFAULT_BROWSER_ID}"), page_ws_url: format!("ws://{addr}/devtools/page/{DEFAULT_TARGET_ID}"), bidi_ws_url: format!("ws://{addr}/session"), diff --git a/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/browser_close.rs b/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/browser_close.rs index d856e70ab2..c21e664f5c 100644 --- a/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/browser_close.rs +++ b/moli-protocol-server/src/protocol_server/tests/cdp_dynamic_page/browser_close.rs @@ -20,6 +20,7 @@ async fn instance( OptionalResourceFetchMask::NONE, ), crate::config::DEFAULT_SCREENCAST_INTERVAL_MS, + BrowserInstanceControl::new(), ); let serving = state.clone(); let task = tokio::spawn(async move { serve_listener(listener, serving).await }); diff --git a/moli-protocol-server/src/protocol_server/tests/classic/extracted/elements.rs b/moli-protocol-server/src/protocol_server/tests/classic/extracted/elements.rs index f71c01cd12..ae2c09a067 100644 --- a/moli-protocol-server/src/protocol_server/tests/classic/extracted/elements.rs +++ b/moli-protocol-server/src/protocol_server/tests/classic/extracted/elements.rs @@ -11,6 +11,7 @@ async fn webdriver_classic_hidden_scrollbars_preserve_server_configuration() { ) .with_scrollbars_hidden(true), crate::config::DEFAULT_SCREENCAST_INTERVAL_MS, + BrowserInstanceControl::new(), ) .unwrap(); let app = build_router(state); @@ -2831,6 +2832,7 @@ async fn webdriver_classic_click_respects_dom_first_and_real_layout_policies() { policy, ), crate::config::DEFAULT_SCREENCAST_INTERVAL_MS, + BrowserInstanceControl::new(), ) .unwrap(); let app = build_router(state); diff --git a/moli-protocol-server/src/protocol_server/tests/mod.rs b/moli-protocol-server/src/protocol_server/tests/mod.rs index f918e932d2..8d043d8573 100644 --- a/moli-protocol-server/src/protocol_server/tests/mod.rs +++ b/moli-protocol-server/src/protocol_server/tests/mod.rs @@ -276,6 +276,7 @@ fn protocol_server_test_state( optional_resource_fetch_mask, ), crate::config::DEFAULT_SCREENCAST_INTERVAL_MS, + BrowserInstanceControl::new(), ) .expect("test app state should initialize") } @@ -437,6 +438,7 @@ async fn spawn_test_protocol_server_with_runtime_config( storage_partition, navigation_runtime_config, crate::config::DEFAULT_SCREENCAST_INTERVAL_MS, + BrowserInstanceControl::new(), ) .expect("test app state should initialize"); let server = tokio::spawn(async move {