From c391bfdd3872409d0814ae3fddf699be22c7fc45 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Mon, 31 Aug 2026 11:42:57 +0800 Subject: [PATCH] refactor(cdp): route page work through explicit owners --- moli-protocol/src/conn.rs | 185 +++++++++--------- .../src/conn/browser_context/page_owner.rs | 18 +- .../src/conn/browser_context/session_owner.rs | 19 +- .../browser_context/target_session_owner.rs | 110 ++++++++++- moli-protocol/src/conn/command_owner_scope.rs | 9 + moli-protocol/src/conn/dispatch.rs | 12 +- .../src/conn/resource_runtime_support.rs | 31 +++ moli-protocol/src/conn/runtime_eval.rs | 23 +++ .../src/conn/tests/resource_runtime.rs | 54 +++++ moli-protocol/src/domains/accessibility.rs | 83 ++++---- .../activity/runtime_command_barrier.rs | 21 +- moli-protocol/src/domains/autofill.rs | 21 +- moli-protocol/src/domains/css.rs | 113 ++++++----- .../src/domains/css/node_references.rs | 20 +- moli-protocol/src/domains/css/style_sheets.rs | 20 +- moli-protocol/src/domains/dom_debugger.rs | 78 ++++---- moli-protocol/src/domains/dom_snapshot.rs | 24 +-- .../src/domains/dom_storage/commands.rs | 69 ++++--- moli-protocol/src/domains/emulation.rs | 20 +- moli-protocol/src/domains/page.rs | 97 +++++---- moli-protocol/src/domains/page/termination.rs | 14 +- .../src/domains/page/tests/lifecycle.rs | 15 +- moli-protocol/src/domains/performance.rs | 183 ++++++++++++----- moli-protocol/src/domains/security.rs | 12 +- moli-protocol/src/domains/storage/ops.rs | 28 ++- moli-protocol/src/domains/target/popup.rs | 19 +- 26 files changed, 865 insertions(+), 433 deletions(-) diff --git a/moli-protocol/src/conn.rs b/moli-protocol/src/conn.rs index 570947270e..38279e81cb 100644 --- a/moli-protocol/src/conn.rs +++ b/moli-protocol/src/conn.rs @@ -1692,9 +1692,21 @@ impl CdpConnection { pub fn has_pending_document_navigation_for_session_owner( &self, session_id: Option<&str>, + ) -> bool { + let none_session_owner_route = self.none_session_owner_route_override(); + self.has_pending_document_navigation_for_route( + session_id, + none_session_owner_route.as_ref(), + ) + } + + pub(crate) fn has_pending_document_navigation_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, ) -> bool { let Some((browser_context_id, target_id)) = - self.target_owner_identity_for_session(session_id) + self.target_owner_identity_for_route(session_id, owner_route) else { return false; }; @@ -1704,14 +1716,22 @@ impl CdpConnection { }) } - fn document_navigation_state_for_session_owner( + fn document_navigation_state_for_route( &self, session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, ) -> DevToolsDocumentNavigationState { - if self.target_owner_identity_for_session(session_id).is_none() { + let Some((browser_context_id, target_id)) = + self.target_owner_identity_for_route(session_id, owner_route) + else { return DevToolsDocumentNavigationState::Unavailable; - } - if self.has_pending_document_navigation_for_session_owner(session_id) { + }; + if self + .browser_context_by_id(&browser_context_id) + .is_some_and(|browser_context| { + browser_context.has_pending_document_navigation_for_target(target_id.as_deref()) + }) + { return DevToolsDocumentNavigationState::PendingNavigation; } // The initial empty Document has a real loader identity before any @@ -1721,34 +1741,26 @@ impl CdpConnection { // Otherwise a materialized `about:blank` is misclassified as // `AwaitingCommit`, and ChromeDriver-style pre-command navigation // waits can never complete. - match self.target_session_owner_frame_tree_loader_id(session_id) { + match self.target_session_owner_frame_tree_loader_id_for_route(session_id, owner_route) { Some(loader_id) => DevToolsDocumentNavigationState::Committed { loader_id }, None => DevToolsDocumentNavigationState::AwaitingCommit, } } /// Resolves Document readiness through the exact target captured by a - /// protocol-neutral command context. A target-id context intentionally - /// uses the owner-route override rather than whichever target is currently - /// active in the browser context. + /// protocol-neutral command context. A target-id context resolves its + /// explicit route rather than whichever target is currently active in the + /// browser context. pub fn devtools_context_document_navigation_state( &mut self, context: &DevToolsCommandContext, ) -> DevToolsDocumentNavigationState { - if let Some(target_id) = context.target_id.as_ref() { - let Some(route) = self.target_session_route_for_target_id(target_id.as_str()) else { - return DevToolsDocumentNavigationState::Unavailable; - }; - let mut route_scope = self.scoped_none_session_owner_route_override(route); - return route_scope - .conn_mut() - .document_navigation_state_for_session_owner(None); - } - self.document_navigation_state_for_session_owner( - context - .session_id - .as_ref() - .map(|session_id| session_id.as_str()), + let Some(owner_scope) = self.command_owner_scope_for_devtools_context(context) else { + return DevToolsDocumentNavigationState::Unavailable; + }; + self.document_navigation_state_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), ) } @@ -1783,7 +1795,16 @@ impl CdpConnection { &self, session_id: Option<&str>, ) -> Result<(), String> { - if self.has_pending_document_navigation_for_session_owner(session_id) { + let none_session_owner_route = self.none_session_owner_route_override(); + self.ensure_document_accessible_for_route(session_id, none_session_owner_route.as_ref()) + } + + pub(crate) fn ensure_document_accessible_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Result<(), String> { + if self.has_pending_document_navigation_for_route(session_id, owner_route) { return Err("Navigation is changing the document".to_owned()); } Ok(()) @@ -1844,8 +1865,20 @@ impl CdpConnection { &mut self, session_id: Option<&str>, ) -> Result<&mut Page, String> { - self.ensure_document_accessible_for_session_owner(session_id)?; - self.loaded_page_mut_for_interruptible_protocol_access(session_id) + let none_session_owner_route = self.none_session_owner_route_override(); + self.loaded_page_mut_for_protocol_access_for_route( + session_id, + none_session_owner_route.as_ref(), + ) + } + + pub(crate) fn loaded_page_mut_for_protocol_access_for_route( + &mut self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Result<&mut Page, String> { + self.ensure_document_accessible_for_route(session_id, owner_route)?; + self.loaded_page_mut_for_interruptible_protocol_access_for_route(session_id, owner_route) } /// Returns the exact Page that remains attached while a cross-Document @@ -1859,7 +1892,19 @@ impl CdpConnection { &mut self, session_id: Option<&str>, ) -> Result<&mut Page, String> { - self.runtime_session_owner_slot_mut(session_id)? + let none_session_owner_route = self.none_session_owner_route_override(); + self.loaded_page_mut_for_interruptible_protocol_access_for_route( + session_id, + none_session_owner_route.as_ref(), + ) + } + + pub(crate) fn loaded_page_mut_for_interruptible_protocol_access_for_route( + &mut self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Result<&mut Page, String> { + self.runtime_session_owner_slot_mut_for_route(session_id, owner_route)? .loaded_page_mut() .ok_or_else(|| "NoDocumentLoaded".to_owned()) } @@ -2231,11 +2276,10 @@ impl CdpConnection { context: &DevToolsCommandContext, ) -> Option { let owner_scope = self.command_owner_scope_for_devtools_context(context)?; - let session_id = owner_scope.session_id().map(str::to_owned); - let mut route_scope = owner_scope.enter(self); - route_scope - .conn_mut() - .target_page_residence_identity_for_session(session_id.as_deref()) + self.target_page_residence_identity_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) } pub fn capture_devtools_document_lifecycle_wait_key( @@ -2244,26 +2288,15 @@ impl CdpConnection { expected_loader_id: &str, milestone: moli_core::page::RendererDocumentLifecycleMilestone, ) -> Option { - let registration = if let Some(target_id) = context.target_id.as_ref() { - let route = self.target_session_route_for_target_id(target_id.as_str())?; - let mut route_scope = self.scoped_none_session_owner_route_override(route); - route_scope - .conn_mut() - .runtime_session_owner_slot_mut(None) - .ok()? - .page_slot_mut() - .register_renderer_document_lifecycle_waiter(milestone, expected_loader_id) - } else { - self.runtime_session_owner_slot_mut( - context - .session_id - .as_ref() - .map(|session_id| session_id.as_str()), + let owner_scope = self.command_owner_scope_for_devtools_context(context)?; + let registration = self + .runtime_session_owner_slot_mut_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), ) .ok()? .page_slot_mut() - .register_renderer_document_lifecycle_waiter(milestone, expected_loader_id) - }; + .register_renderer_document_lifecycle_waiter(milestone, expected_loader_id); let (registration_id, binding) = registration?; Some(DevToolsDocumentLifecycleWaitKey { registration_id, @@ -2280,24 +2313,12 @@ impl CdpConnection { context: &DevToolsCommandContext, key: &DevToolsDocumentLifecycleWaitKey, ) -> DevToolsDocumentLifecycleWaitState { - if let Some(target_id) = context.target_id.as_ref() { - let Some(route) = self.target_session_route_for_target_id(target_id.as_str()) else { - return DevToolsDocumentLifecycleWaitState::Unavailable; - }; - let mut route_scope = self.scoped_none_session_owner_route_override(route); - return route_scope - .conn_mut() - .runtime_session_owner_slot(None) - .ok() - .map_or(DevToolsDocumentLifecycleWaitState::Unavailable, |slot| { - devtools_document_lifecycle_wait_state_for_slot(slot, key) - }); - } - self.runtime_session_owner_slot( - context - .session_id - .as_ref() - .map(|session_id| session_id.as_str()), + let Some(owner_scope) = self.command_owner_scope_for_devtools_context(context) else { + return DevToolsDocumentLifecycleWaitState::Unavailable; + }; + self.runtime_session_owner_slot_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), ) .ok() .map_or(DevToolsDocumentLifecycleWaitState::Unavailable, |slot| { @@ -2310,30 +2331,12 @@ impl CdpConnection { context: &DevToolsCommandContext, key: &DevToolsDocumentLifecycleWaitKey, ) -> bool { - if let Some(target_id) = context.target_id.as_ref() { - let Some(route) = self.target_session_route_for_target_id(target_id.as_str()) else { - return false; - }; - let mut route_scope = self.scoped_none_session_owner_route_override(route); - return route_scope - .conn_mut() - .runtime_session_owner_slot_mut(None) - .is_ok_and(|slot| { - slot.page_slot_mut() - .release_renderer_document_lifecycle_waiter( - key.registration_id, - key.renderer_document, - key.renderer_epoch, - &key.frame_id, - &key.loader_id, - ) - }); - } - self.runtime_session_owner_slot_mut( - context - .session_id - .as_ref() - .map(|session_id| session_id.as_str()), + let Some(owner_scope) = self.command_owner_scope_for_devtools_context(context) else { + return false; + }; + self.runtime_session_owner_slot_mut_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), ) .is_ok_and(|slot| { slot.page_slot_mut() diff --git a/moli-protocol/src/conn/browser_context/page_owner.rs b/moli-protocol/src/conn/browser_context/page_owner.rs index f8ff48ea91..15e744eb10 100644 --- a/moli-protocol/src/conn/browser_context/page_owner.rs +++ b/moli-protocol/src/conn/browser_context/page_owner.rs @@ -913,27 +913,25 @@ impl CdpConnection { .unwrap_or(false) } - pub(crate) fn begin_page_screencast_capture_for_session_owner( + pub(crate) fn begin_page_screencast_capture_for_route( &mut self, session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, generation: i32, ) -> Option { - self.with_target_session_owner_mut(session_id, |owner| { - owner.begin_page_screencast_capture(generation) - }) - .flatten() + self.target_session_owner_mut_for_route(session_id, owner_route)? + .begin_page_screencast_capture(generation) } - pub(crate) fn complete_page_screencast_capture_for_session_owner( + pub(crate) fn complete_page_screencast_capture_for_route( &mut self, session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, generation: i32, frame_emitted: bool, ) -> Option { - self.with_target_session_owner_mut(session_id, |owner| { - owner.complete_page_screencast_capture(generation, frame_emitted) - }) - .flatten() + self.target_session_owner_mut_for_route(session_id, owner_route)? + .complete_page_screencast_capture(generation, frame_emitted) } pub(crate) fn acknowledge_page_screencast_frame_for_session_owner( diff --git a/moli-protocol/src/conn/browser_context/session_owner.rs b/moli-protocol/src/conn/browser_context/session_owner.rs index 2727809df8..25fab83ca3 100644 --- a/moli-protocol/src/conn/browser_context/session_owner.rs +++ b/moli-protocol/src/conn/browser_context/session_owner.rs @@ -38,11 +38,26 @@ impl CdpConnection { pub(super) fn target_session_owner( &self, session_id: Option<&str>, + ) -> Option { + let none_session_owner_route = self.none_session_owner_route_override(); + self.target_session_owner_for_route(session_id, none_session_owner_route.as_ref()) + } + + /// Resolves target ownership from an explicit command route. + /// + /// This is the non-ambient owner boundary used while migrating deferred + /// commands away from `none_session_owner_route_override`. A concrete + /// session always resolves through the session registry; `owner_route` + /// only selects the owner of an implicit primary Page attachment. + pub(super) fn target_session_owner_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, ) -> Option { let route = match session_id { Some(session_id) => self.session_route(Some(session_id))?, - None => match self.none_session_owner_route_override() { - Some(route) => route, + None => match owner_route { + Some(route) => route.clone(), None => { let Some(browser_context) = self.browser_context.as_ref() else { return Some(TargetSessionOwner::NoLoadedBrowserContext); diff --git a/moli-protocol/src/conn/browser_context/target_session_owner.rs b/moli-protocol/src/conn/browser_context/target_session_owner.rs index 7cdf1baf15..d087214e4c 100644 --- a/moli-protocol/src/conn/browser_context/target_session_owner.rs +++ b/moli-protocol/src/conn/browser_context/target_session_owner.rs @@ -2470,16 +2470,17 @@ impl CdpConnection { .await } - pub(crate) async fn close_page_target_for_session_owner_async( + pub(crate) async fn close_page_target_for_route_async( &mut self, session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, ) -> Option { let collected_network_data_artifacts = self - .target_session_owner_ref(session_id)? + .target_session_owner_ref_for_route(session_id, owner_route)? .runtime_slot()? .collected_network_data_artifacts(); let closed = self - .target_session_owner_mut(session_id)? + .target_session_owner_mut_for_route(session_id, owner_route)? .close_page_target_async() .await?; self.record_collected_network_data_artifacts(collected_network_data_artifacts); @@ -2676,6 +2677,15 @@ impl CdpConnection { .page_session_state() } + pub(crate) fn target_page_session_state_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Option<&TargetPageSessionState> { + self.target_session_owner_ref_for_route(session_id, owner_route)? + .page_session_state() + } + pub(crate) fn target_devtools_session_state_for_session( &self, session_id: Option<&str>, @@ -2789,6 +2799,16 @@ impl CdpConnection { .mutate_session_state(|mut state| state.devtools_session_state_mut().map(f)) } + pub(crate) fn with_target_devtools_session_state_for_route_mut( + &mut self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + f: impl FnOnce(&mut DevToolsSessionState) -> R, + ) -> Option { + self.target_session_owner_mut_for_route(session_id, owner_route)? + .mutate_session_state(|mut state| state.devtools_session_state_mut().map(f)) + } + pub(crate) fn target_owner_identity_for_session( &self, session_id: Option<&str>, @@ -2796,6 +2816,15 @@ impl CdpConnection { self.target_session_owner_ref(session_id)?.owner_identity() } + pub(crate) fn target_owner_identity_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Option<(String, Option)> { + self.target_session_owner_ref_for_route(session_id, owner_route)? + .owner_identity() + } + /// Captures the exact target-local Page residence currently addressed by /// `session_id`. /// @@ -2806,9 +2835,18 @@ impl CdpConnection { pub(crate) fn target_page_residence_identity_for_session( &self, session_id: Option<&str>, + ) -> Option { + let none_session_owner_route = self.none_session_owner_route_override(); + self.target_page_residence_identity_for_route(session_id, none_session_owner_route.as_ref()) + } + + pub(crate) fn target_page_residence_identity_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, ) -> Option { let (browser_context_id, routed_target_id) = - self.target_owner_identity_for_session(session_id)?; + self.target_owner_identity_for_route(session_id, owner_route)?; // `None` in a `CdpSessionRoute::ActiveTarget` is a routing shorthand: // it means "whichever target is currently active". A Page residence // identity must never retain that mutable shorthand. Freeze the @@ -2821,7 +2859,7 @@ impl CdpConnection { .map(str::to_owned) }); let page_attachment_id = self - .runtime_session_owner_slot(session_id) + .runtime_session_owner_slot_for_route(session_id, owner_route) .ok()? .page_attachment_id()?; Some(TargetPageResidenceIdentity::new( @@ -3121,11 +3159,20 @@ impl CdpConnection { pub(crate) fn runtime_session_owner_slot_mut( &mut self, session_id: Option<&str>, + ) -> Result<&mut TargetRuntimeSlot, String> { + let none_session_owner_route = self.none_session_owner_route_override(); + self.runtime_session_owner_slot_mut_for_route(session_id, none_session_owner_route.as_ref()) + } + + pub(crate) fn runtime_session_owner_slot_mut_for_route( + &mut self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, ) -> Result<&mut TargetRuntimeSlot, String> { let renderer_inspector_session_id = self.target_renderer_runtime_inspector_session_id_for_session(session_id); let slot = self - .target_session_owner_mut(session_id) + .target_session_owner_mut_for_route(session_id, owner_route) .and_then(TargetSessionOwnerMut::into_runtime_slot_mut) .ok_or_else(|| "NoDocumentLoaded".to_owned())?; if let Some(page) = slot.loaded_page_mut() { @@ -3138,7 +3185,16 @@ impl CdpConnection { &self, session_id: Option<&str>, ) -> Result<&TargetRuntimeSlot, String> { - self.target_session_owner_ref(session_id) + let none_session_owner_route = self.none_session_owner_route_override(); + self.runtime_session_owner_slot_for_route(session_id, none_session_owner_route.as_ref()) + } + + pub(crate) fn runtime_session_owner_slot_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Result<&TargetRuntimeSlot, String> { + self.target_session_owner_ref_for_route(session_id, owner_route) .and_then(|owner| owner.runtime_slot()) .ok_or_else(|| "NoDocumentLoaded".to_owned()) } @@ -3315,6 +3371,15 @@ impl CdpConnection { .frame_tree_loader_id() } + pub(crate) fn target_session_owner_frame_tree_loader_id_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Option { + self.target_session_owner_ref_for_route(session_id, owner_route)? + .frame_tree_loader_id() + } + pub(crate) fn target_session_owner_emulated_device_metrics( &self, session_id: Option<&str>, @@ -3323,6 +3388,15 @@ impl CdpConnection { .emulated_device_metrics() } + pub(crate) fn target_session_owner_emulated_device_metrics_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Option { + self.target_session_owner_ref_for_route(session_id, owner_route)? + .emulated_device_metrics() + } + pub(crate) fn target_session_owner_navigation_history_snapshot( &mut self, session_id: Option<&str>, @@ -3400,7 +3474,16 @@ impl CdpConnection { &mut self, session_id: Option<&str>, ) -> Option> { - match self.target_session_owner(session_id)? { + let none_session_owner_route = self.none_session_owner_route_override(); + self.target_session_owner_mut_for_route(session_id, none_session_owner_route.as_ref()) + } + + pub(super) fn target_session_owner_mut_for_route( + &mut self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Option> { + match self.target_session_owner_for_route(session_id, owner_route)? { TargetSessionOwner::ActiveTarget { browser_context_id, is_auxiliary_target_session, @@ -3439,7 +3522,16 @@ impl CdpConnection { &self, session_id: Option<&str>, ) -> Option> { - match self.target_session_owner(session_id)? { + let none_session_owner_route = self.none_session_owner_route_override(); + self.target_session_owner_ref_for_route(session_id, none_session_owner_route.as_ref()) + } + + pub(super) fn target_session_owner_ref_for_route( + &self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + ) -> Option> { + match self.target_session_owner_for_route(session_id, owner_route)? { TargetSessionOwner::ActiveTarget { browser_context_id, is_auxiliary_target_session, diff --git a/moli-protocol/src/conn/command_owner_scope.rs b/moli-protocol/src/conn/command_owner_scope.rs index a72fd41e4a..7eaf42342b 100644 --- a/moli-protocol/src/conn/command_owner_scope.rs +++ b/moli-protocol/src/conn/command_owner_scope.rs @@ -32,6 +32,15 @@ impl CommandOwnerScope { self.session_id.as_deref() } + /// Returns the exact route captured for an implicit-session command. + /// + /// A concrete CDP session remains authoritative through `session_id`; the + /// route is only needed for protocol-neutral and deferred work which uses + /// Chromium's implicit primary Page attachment. + pub(crate) fn session_owner_route(&self) -> Option<&CdpSessionRoute> { + self.session_owner_route.as_ref() + } + pub(crate) fn enter<'a>( &self, conn: &'a mut CdpConnection, diff --git a/moli-protocol/src/conn/dispatch.rs b/moli-protocol/src/conn/dispatch.rs index 9fb85296f7..865166adbf 100644 --- a/moli-protocol/src/conn/dispatch.rs +++ b/moli-protocol/src/conn/dispatch.rs @@ -108,16 +108,16 @@ impl PendingCdpCommandDispatchKind { fn owner_scope_capture_session_id(&self) -> Option> { match self { - Self::Autofill(pending) => Some(pending.session_id()), - Self::Accessibility(pending) => Some(pending.session_id()), Self::Input(pending) => Some(pending.session_id()), - Self::Css(pending) => Some(pending.session_id()), Self::Dom(pending) => Some(pending.session_id()), - Self::DomDebugger(pending) => Some(pending.session_id()), - Self::DomSnapshot(pending) => Some(pending.session_id()), - Self::Performance(pending) => Some(pending.session_id()), Self::Tracing(pending) => Some(pending.session_id()), Self::Runtime(_) + | Self::Autofill(_) + | Self::Accessibility(_) + | Self::Css(_) + | Self::DomDebugger(_) + | Self::DomSnapshot(_) + | Self::Performance(_) | Self::DomStorage(_) | Self::Page(_) | Self::Emulation(_) diff --git a/moli-protocol/src/conn/resource_runtime_support.rs b/moli-protocol/src/conn/resource_runtime_support.rs index 201a3f2e64..dc37630127 100644 --- a/moli-protocol/src/conn/resource_runtime_support.rs +++ b/moli-protocol/src/conn/resource_runtime_support.rs @@ -256,6 +256,19 @@ impl CdpConnection { .map_err(|error| format!("failed to update page resource runtime: {error}")) } + pub(crate) fn finish_rebuild_resource_runtime_for_route( + &mut self, + session_id: Option<&str>, + owner_route: Option<&super::CdpSessionRoute>, + completion: CompletedPageCommand, + ) -> Result<(), String> { + let Some(page) = self.resource_runtime_apply_page_for_route(session_id, owner_route) else { + return Ok(()); + }; + page.finish_replace_browser_resource_runtime(completion) + .map_err(|error| format!("failed to update page resource runtime: {error}")) + } + fn resource_runtime_apply_page_for_session_owner( &mut self, session_id: Option<&str>, @@ -271,4 +284,22 @@ impl CdpConnection { } self.loaded_page_mut_for_protocol_access(session_id).ok() } + + fn resource_runtime_apply_page_for_route( + &mut self, + session_id: Option<&str>, + owner_route: Option<&super::CdpSessionRoute>, + ) -> Option<&mut moli_core::page::Page> { + if matches!( + session_id.and_then(|session_id| self.session_route(Some(session_id))), + Some(super::CdpSessionRoute::Browser) + ) { + return self + .browser_context + .as_mut() + .and_then(|bc| bc.active_target.runtime_slot.loaded_page_mut()); + } + self.loaded_page_mut_for_protocol_access_for_route(session_id, owner_route) + .ok() + } } diff --git a/moli-protocol/src/conn/runtime_eval.rs b/moli-protocol/src/conn/runtime_eval.rs index 389ecd0782..bd8dfeee18 100644 --- a/moli-protocol/src/conn/runtime_eval.rs +++ b/moli-protocol/src/conn/runtime_eval.rs @@ -1042,6 +1042,29 @@ impl CdpConnection { ) == Some(correlation) } + pub(crate) fn take_renderer_call_if_correlation_matches_for_route( + &mut self, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, + correlation: RendererCommandCorrelation, + ) -> bool { + if session_id.is_some() { + return self.take_renderer_call_if_correlation_matches_for_session_owner( + session_id, + correlation, + ); + } + self.with_target_devtools_session_state_for_route_mut(session_id, owner_route, |state| { + state.take_renderer_call_for_frontend_if_matches( + correlation.frontend_command_id().get(), + correlation.renderer_call_id(), + correlation.dispatched_attachment_id(), + ) + }) + .flatten() + == Some(correlation) + } + fn take_frontend_command_for_renderer_if_attachment_matches_for_session_owner( &mut self, session_id: Option<&str>, diff --git a/moli-protocol/src/conn/tests/resource_runtime.rs b/moli-protocol/src/conn/tests/resource_runtime.rs index 1a63d92cc8..51615bcd68 100644 --- a/moli-protocol/src/conn/tests/resource_runtime.rs +++ b/moli-protocol/src/conn/tests/resource_runtime.rs @@ -1705,6 +1705,60 @@ fn devtools_document_lifecycle_wait_key_observes_interruption_and_target_loss() ); } +#[test] +fn devtools_target_context_resolves_background_page_without_ambient_route() { + let mut conn = CdpConnection::new(); + let mut browser_context = BrowserContext::new("BID-explicit-owner".into()); + browser_context.set_active_target_id("TID-active"); + browser_context + .active_target + .runtime_slot + .set_page_attachment_id_for_test(1001); + assert!( + browser_context.insert_page_target_host(PageTargetHost::with_url( + "TID-background".to_owned(), + None, + "about:blank".to_owned(), + )) + ); + let background = browser_context + .background_target_mut("TID-background") + .expect("background PageTargetHost"); + background + .active_target + .runtime_slot + .set_page_attachment_id_for_test(1002); + background + .active_target + .runtime_slot + .start_document_navigation("TID-background".to_owned(), "LID-background".to_owned()); + conn.browser_context = Some(browser_context); + + let context = crate::devtools_runtime::DevToolsCommandContext { + protocol: crate::devtools_runtime::DevToolsProtocol::WebDriverBidi, + session_id: None, + target_id: Some(crate::devtools_runtime::DevToolsTargetId::from( + "TID-background", + )), + browser_context_id: None, + }; + + assert_eq!(conn.none_session_owner_route_override(), None); + let residence = conn + .page_residence_identity_for_devtools_context(&context) + .expect("explicit target context should resolve its Page"); + assert_eq!(residence.target_id(), Some("TID-background")); + assert_eq!( + conn.devtools_context_document_navigation_state(&context), + crate::DevToolsDocumentNavigationState::PendingNavigation + ); + assert_eq!( + conn.none_session_owner_route_override(), + None, + "explicit lookup must not mutate connection-global routing state" + ); +} + #[tokio::test] async fn direct_runtime_evaluate_same_document_navigation_updates_inactive_owner() { let mut ctx = crate::testing::TestContext::new(); diff --git a/moli-protocol/src/domains/accessibility.rs b/moli-protocol/src/domains/accessibility.rs index 83185f0d38..e17e9ee658 100644 --- a/moli-protocol/src/domains/accessibility.rs +++ b/moli-protocol/src/domains/accessibility.rs @@ -1,4 +1,4 @@ -use crate::conn::{CdpConnection, Cmd}; +use crate::conn::{CdpConnection, Cmd, CommandOwnerScope}; use crate::domains::actions::AccessibilityAction; use crate::domains::command_output::CommandOutputPlan; use moli_core::page::{ @@ -12,14 +12,14 @@ mod tests; pub(crate) struct PendingAccessibilityCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, kind: PendingAccessibilityCommandKind, pending: PendingAccessibilityCommandWork, } pub(crate) struct CompletedAccessibilityCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, kind: PendingAccessibilityCommandKind, completed: CompletedAccessibilityCommandWork, } @@ -82,8 +82,18 @@ struct PendingAccessibilityCommandStartError { } impl PendingAccessibilityCommandDispatch { - pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() + fn from_command( + conn: &CdpConnection, + cmd: &Cmd<'_>, + kind: PendingAccessibilityCommandKind, + pending: PendingAccessibilityCommandWork, + ) -> Self { + Self { + command_id: cmd.id, + owner_scope: CommandOwnerScope::capture(conn, cmd.session_id), + kind, + pending, + } } pub async fn wait(self) -> CompletedAccessibilityCommandDispatch { @@ -96,7 +106,7 @@ impl PendingAccessibilityCommandDispatch { }; CompletedAccessibilityCommandDispatch { command_id: self.command_id, - session_id: self.session_id, + owner_scope: self.owner_scope, kind: self.kind, completed, } @@ -109,7 +119,7 @@ impl CompletedAccessibilityCommandDispatch { } pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() + self.owner_scope.session_id() } } @@ -437,16 +447,16 @@ fn start_pending_object_reference_command( &operation, ) .map_err(PendingAccessibilityCommandStartError::renderer_error)?; - Ok(Some(PendingAccessibilityCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingAccessibilityCommandKind::ObjectAccessibilityPayloads { + Ok(Some(PendingAccessibilityCommandDispatch::from_command( + conn, + cmd, + PendingAccessibilityCommandKind::ObjectAccessibilityPayloads { frame_id: resolved_frame_id, top_frame_id, operation, }, - pending: PendingAccessibilityCommandWork::Page(pending), - })) + PendingAccessibilityCommandWork::Page(pending), + ))) } fn renderer_backend_node_id_for_reference(reference: &helpers::NodeReferenceParams) -> Option { @@ -481,16 +491,16 @@ fn start_pending_dom_node_reference_command( let pending = page .start_document_frontend_node_binding(renderer_inspector_session_id, frontend_node_id) .map_err(PendingAccessibilityCommandStartError::renderer_error)?; - Ok(Some(PendingAccessibilityCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingAccessibilityCommandKind::FrontendAccessibilityPayloads { + Ok(Some(PendingAccessibilityCommandDispatch::from_command( + conn, + cmd, + PendingAccessibilityCommandKind::FrontendAccessibilityPayloads { frame_id: resolved_frame_id, top_frame_id, operation, }, - pending: PendingAccessibilityCommandWork::Page(pending), - })) + PendingAccessibilityCommandWork::Page(pending), + ))) } fn start_accessibility_object_page_command( @@ -545,16 +555,16 @@ fn start_pending_backend_reference_command( }; let pending = start_accessibility_backend_page_command(page, backend_node_id, &operation) .map_err(PendingAccessibilityCommandStartError::renderer_error)?; - Ok(Some(PendingAccessibilityCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingAccessibilityCommandKind::BackendAccessibilityPayloads { + Ok(Some(PendingAccessibilityCommandDispatch::from_command( + conn, + cmd, + PendingAccessibilityCommandKind::BackendAccessibilityPayloads { frame_id: resolved_frame_id, top_frame_id, operation, }, - pending: PendingAccessibilityCommandWork::Page(pending), - })) + PendingAccessibilityCommandWork::Page(pending), + ))) } fn start_accessibility_backend_page_command( @@ -631,12 +641,12 @@ fn start_pending_frame_scoped_accessibility_command( .map_err(PendingAccessibilityCommandStartError::renderer_error)?; (child_frame_kind, pending) }; - Ok(Some(PendingAccessibilityCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), + Ok(Some(PendingAccessibilityCommandDispatch::from_command( + conn, + cmd, kind, - pending: PendingAccessibilityCommandWork::Page(pending), - })) + PendingAccessibilityCommandWork::Page(pending), + ))) } fn start_child_frame_accessibility_page_command( @@ -661,20 +671,25 @@ pub(crate) async fn complete_pending_accessibility_command( ) -> AccessibilityCommandDispatchStep { let CompletedAccessibilityCommandDispatch { command_id, - session_id, + owner_scope, kind, completed, } = completed; let CompletedAccessibilityCommandWork::Page(completed) = completed; let completed = *completed; + let session_id = owner_scope.session_id().map(str::to_owned); let session_id_ref = session_id.as_deref(); - if let Err(message) = conn.ensure_document_accessible_for_session_owner(session_id_ref) { + let owner_route = owner_scope.session_owner_route(); + if let Err(message) = conn.ensure_document_accessible_for_route(session_id_ref, owner_route) { return AccessibilityCommandDispatchStep::Complete(CommandOutputPlan::error( -32000, message, )); } - let Some(page) = helpers::loaded_page_mut_for_session(conn, session_id_ref) else { + let Some(page) = conn + .loaded_page_mut_for_protocol_access_for_route(session_id_ref, owner_route) + .ok() + else { return AccessibilityCommandDispatchStep::Complete(CommandOutputPlan::error( -32000, "NoDocumentLoaded", @@ -785,7 +800,7 @@ pub(crate) async fn complete_pending_accessibility_command( return AccessibilityCommandDispatchStep::Pending( PendingAccessibilityCommandDispatch { command_id, - session_id, + owner_scope, kind: PendingAccessibilityCommandKind::BackendAccessibilityPayloads { frame_id, top_frame_id, diff --git a/moli-protocol/src/domains/activity/runtime_command_barrier.rs b/moli-protocol/src/domains/activity/runtime_command_barrier.rs index 0aef7f1cfd..b5713e1f01 100644 --- a/moli-protocol/src/domains/activity/runtime_command_barrier.rs +++ b/moli-protocol/src/domains/activity/runtime_command_barrier.rs @@ -155,6 +155,21 @@ impl RuntimeCommandCausalOwner { target_id, }) } + + fn capture_for_scope(conn: &CdpConnection, scope: &CommandOwnerScope) -> Option { + if let Some(page) = conn.target_page_residence_identity_for_route( + scope.session_id(), + scope.session_owner_route(), + ) { + return Some(Self::Page(page)); + } + let (browser_context_id, target_id) = + conn.target_owner_identity_for_route(scope.session_id(), scope.session_owner_route())?; + Some(Self::Target { + browser_context_id, + target_id, + }) + } } impl RuntimeCommandOutputRoute { @@ -360,12 +375,10 @@ impl RuntimeCommandOutputBarriers { fn command_owner_is_current( &self, - conn: &mut CdpConnection, + conn: &CdpConnection, barrier: &ActiveRuntimeCommandOutputBarrier, ) -> bool { - let mut scope = barrier.command_scope.enter(conn); - RuntimeCommandCausalOwner::capture(scope.conn_mut(), barrier.command_scope.session_id()) - .as_ref() + RuntimeCommandCausalOwner::capture_for_scope(conn, &barrier.command_scope).as_ref() == Some(&barrier.causal_owner) } diff --git a/moli-protocol/src/domains/autofill.rs b/moli-protocol/src/domains/autofill.rs index 57e57c631d..0dd3c1771c 100644 --- a/moli-protocol/src/domains/autofill.rs +++ b/moli-protocol/src/domains/autofill.rs @@ -5,19 +5,19 @@ use moli_core::page::{ }; use crate::{ - conn::{CdpConnection, Cmd}, + conn::{CdpConnection, Cmd, CommandOwnerScope}, domains::{actions::AutofillAction, command_output::CommandOutputPlan}, }; pub(crate) struct PendingAutofillCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, pending: PendingPageCommand, } pub(crate) struct CompletedAutofillCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, completed: Result, } @@ -27,14 +27,10 @@ pub(crate) enum AutofillCommandTaskStep { } impl PendingAutofillCommandDispatch { - pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() - } - pub(crate) async fn wait(self) -> CompletedAutofillCommandDispatch { CompletedAutofillCommandDispatch { command_id: self.command_id, - session_id: self.session_id, + owner_scope: self.owner_scope, completed: self.pending.wait().await.map_err(|error| error.to_string()), } } @@ -46,7 +42,7 @@ impl CompletedAutofillCommandDispatch { } pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() + self.owner_scope.session_id() } } @@ -111,6 +107,7 @@ pub(crate) fn try_start_autofill_command_dispatch( card, address, }; + let owner_scope = CommandOwnerScope::capture(conn, cmd.session_id); let pending = conn .loaded_page_mut_for_protocol_access(cmd.session_id) .and_then(|page| { @@ -120,7 +117,7 @@ pub(crate) fn try_start_autofill_command_dispatch( match pending { Ok(pending) => AutofillCommandTaskStep::Pending(PendingAutofillCommandDispatch { command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), + owner_scope, pending, }), Err(message) => { @@ -133,8 +130,10 @@ pub(crate) fn complete_pending_autofill_command( conn: &mut CdpConnection, completed: CompletedAutofillCommandDispatch, ) -> CommandOutputPlan { + let session_id = completed.owner_scope.session_id(); + let owner_route = completed.owner_scope.session_owner_route(); let outcome = completed.completed.and_then(|completion| { - conn.loaded_page_mut_for_protocol_access(completed.session_id.as_deref()) + conn.loaded_page_mut_for_protocol_access_for_route(session_id, owner_route) .and_then(|page| { page.finish_autofill_trigger(completion) .map_err(|error| error.to_string()) diff --git a/moli-protocol/src/domains/css.rs b/moli-protocol/src/domains/css.rs index 6ecc2b964e..ee0a33e937 100644 --- a/moli-protocol/src/domains/css.rs +++ b/moli-protocol/src/domains/css.rs @@ -1,4 +1,4 @@ -use crate::conn::{CdpConnection, Cmd}; +use crate::conn::{CdpConnection, Cmd, CommandOwnerScope}; use crate::domains::actions::CssAction; use crate::domains::command_output::CommandOutputPlan; use chromiumoxide_cdp::cdp::browser_protocol::css::{ @@ -27,14 +27,14 @@ struct NodeReferenceParams { pub(crate) struct PendingCssCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, kind: PendingCssCommandKind, pending: PendingPageCommand, } pub(crate) struct CompletedCssCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, kind: PendingCssCommandKind, completed: Result, } @@ -78,14 +78,24 @@ struct PendingCssCommandStartError { } impl PendingCssCommandDispatch { - pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() + fn from_command( + conn: &CdpConnection, + cmd: &Cmd<'_>, + kind: PendingCssCommandKind, + pending: PendingPageCommand, + ) -> Self { + Self { + command_id: cmd.id, + owner_scope: CommandOwnerScope::capture(conn, cmd.session_id), + kind, + pending, + } } pub async fn wait(self) -> CompletedCssCommandDispatch { CompletedCssCommandDispatch { command_id: self.command_id, - session_id: self.session_id, + owner_scope: self.owner_scope, kind: self.kind, completed: self.pending.wait().await.map_err(|error| error.to_string()), } @@ -98,7 +108,7 @@ impl CompletedCssCommandDispatch { } pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() + self.owner_scope.session_id() } } @@ -251,15 +261,15 @@ fn start_pending_get_style_sheet_command( &style_sheet_id, ) .map_err(PendingCssCommandStartError::renderer_error)?; - Ok(PendingCssCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingCssCommandKind::GetStyleSheet { + Ok(PendingCssCommandDispatch::from_command( + conn, + cmd, + PendingCssCommandKind::GetStyleSheet { style_sheet_id, frame_id, }, pending, - }) + )) } fn start_pending_set_style_sheet_text_command( @@ -283,12 +293,12 @@ fn start_pending_set_style_sheet_text_command( ¶ms.text, ) .map_err(PendingCssCommandStartError::renderer_error)?; - Ok(PendingCssCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingCssCommandKind::SetStyleSheetText { style_sheet_id }, + Ok(PendingCssCommandDispatch::from_command( + conn, + cmd, + PendingCssCommandKind::SetStyleSheetText { style_sheet_id }, pending, - }) + )) } fn start_pending_get_computed_style_for_node_command( @@ -309,12 +319,12 @@ fn start_pending_get_computed_style_for_node_command( object_id, ) .map_err(PendingCssCommandStartError::renderer_error)?; - return Ok(PendingCssCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingCssCommandKind::GetComputedStyleForNode, + return Ok(PendingCssCommandDispatch::from_command( + conn, + cmd, + PendingCssCommandKind::GetComputedStyleForNode, pending, - }); + )); } if let Some(cdp_node_id) = params.node_id { return node_references::start_frontend_node_binding_for_computed_style( @@ -332,12 +342,12 @@ fn start_pending_get_computed_style_for_node_command( return Err(PendingCssCommandStartError::node_not_found()); } .map_err(PendingCssCommandStartError::renderer_error)?; - Ok(PendingCssCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingCssCommandKind::GetComputedStyleForNode, + Ok(PendingCssCommandDispatch::from_command( + conn, + cmd, + PendingCssCommandKind::GetComputedStyleForNode, pending, - }) + )) } fn start_pending_inline_style_command( @@ -369,29 +379,40 @@ fn start_pending_inline_style_command( return Err(PendingCssCommandStartError::node_not_found()); } .map_err(PendingCssCommandStartError::renderer_error)?; - Ok(Some(PendingCssCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingCssCommandKind::GetInlineStyleForNode { kind }, + Ok(Some(PendingCssCommandDispatch::from_command( + conn, + cmd, + PendingCssCommandKind::GetInlineStyleForNode { kind }, pending, - })) + ))) } pub(crate) fn complete_pending_css_command( conn: &mut CdpConnection, completed: CompletedCssCommandDispatch, ) -> CssCommandDispatchStep { - let command_id = completed.command_id; - let session_id = completed.session_id.as_deref(); - let Some(page) = loaded_page_mut_for_session(conn, session_id) else { + let CompletedCssCommandDispatch { + command_id, + owner_scope, + kind, + completed, + } = completed; + let session_id = owner_scope.session_id(); + let Some(page) = conn + .loaded_page_mut_for_protocol_access_for_route( + session_id, + owner_scope.session_owner_route(), + ) + .ok() + else { return CssCommandDispatchStep::Complete(CommandOutputPlan::error( -32000, "NoDocumentLoaded", )); }; - match completed.kind { + match kind { PendingCssCommandKind::Enable { frame_id } => { - let completion = match completed.completed { + let completion = match completed { Ok(completion) => completion, Err(error) => { return CssCommandDispatchStep::Complete(CommandOutputPlan::error( @@ -409,7 +430,7 @@ pub(crate) fn complete_pending_css_command( ) } PendingCssCommandKind::Disable => { - let completion = match completed.completed { + let completion = match completed { Ok(completion) => completion, Err(error) => { return CssCommandDispatchStep::Complete(CommandOutputPlan::error( @@ -425,7 +446,7 @@ pub(crate) fn complete_pending_css_command( ) } PendingCssCommandKind::ResolveFrontendNodeForComputedStyle => { - let completion = match completed.completed { + let completion = match completed { Ok(completion) => completion, Err(error) => { return CssCommandDispatchStep::Complete(CommandOutputPlan::error( @@ -464,13 +485,13 @@ pub(crate) fn complete_pending_css_command( }; CssCommandDispatchStep::Pending(PendingCssCommandDispatch { command_id, - session_id: session_id.map(str::to_owned), + owner_scope, kind: PendingCssCommandKind::GetComputedStyleForNode, pending, }) } PendingCssCommandKind::ResolveFrontendNodeForInlineStyle { kind } => { - let completion = match completed.completed { + let completion = match completed { Ok(completion) => completion, Err(error) => { return CssCommandDispatchStep::Complete(CommandOutputPlan::error( @@ -509,7 +530,7 @@ pub(crate) fn complete_pending_css_command( }; CssCommandDispatchStep::Pending(PendingCssCommandDispatch { command_id, - session_id: session_id.map(str::to_owned), + owner_scope, kind: PendingCssCommandKind::GetInlineStyleForNode { kind }, pending, }) @@ -518,7 +539,7 @@ pub(crate) fn complete_pending_css_command( style_sheet_id, frame_id, } => { - let completion = match completed.completed { + let completion = match completed { Ok(completion) => completion, Err(error) => { return CssCommandDispatchStep::Complete(CommandOutputPlan::error( @@ -539,7 +560,7 @@ pub(crate) fn complete_pending_css_command( }) } PendingCssCommandKind::SetStyleSheetText { style_sheet_id } => { - let completion = match completed.completed { + let completion = match completed { Ok(completion) => completion, Err(error) => { return CssCommandDispatchStep::Complete(CommandOutputPlan::error( @@ -561,7 +582,7 @@ pub(crate) fn complete_pending_css_command( ) } PendingCssCommandKind::GetComputedStyleForNode => { - let completion = match completed.completed { + let completion = match completed { Ok(completion) => completion, Err(error) => { return CssCommandDispatchStep::Complete(CommandOutputPlan::error( @@ -583,7 +604,7 @@ pub(crate) fn complete_pending_css_command( ) } PendingCssCommandKind::GetInlineStyleForNode { kind } => { - let completion = match completed.completed { + let completion = match completed { Ok(completion) => completion, Err(error) => { return CssCommandDispatchStep::Complete(CommandOutputPlan::error( diff --git a/moli-protocol/src/domains/css/node_references.rs b/moli-protocol/src/domains/css/node_references.rs index 22a3641e7a..4238107a88 100644 --- a/moli-protocol/src/domains/css/node_references.rs +++ b/moli-protocol/src/domains/css/node_references.rs @@ -18,12 +18,12 @@ pub(super) fn start_frontend_node_binding_for_computed_style( let pending = page .start_document_frontend_node_binding(renderer_inspector_session_id, frontend_node_id) .map_err(PendingCssCommandStartError::renderer_error)?; - Ok(PendingCssCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingCssCommandKind::ResolveFrontendNodeForComputedStyle, + Ok(PendingCssCommandDispatch::from_command( + conn, + cmd, + PendingCssCommandKind::ResolveFrontendNodeForComputedStyle, pending, - }) + )) } pub(super) fn start_frontend_node_binding_for_inline_style( @@ -40,12 +40,12 @@ pub(super) fn start_frontend_node_binding_for_inline_style( let pending = page .start_document_frontend_node_binding(renderer_inspector_session_id, frontend_node_id) .map_err(PendingCssCommandStartError::renderer_error)?; - Ok(Some(PendingCssCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingCssCommandKind::ResolveFrontendNodeForInlineStyle { kind }, + Ok(Some(PendingCssCommandDispatch::from_command( + conn, + cmd, + PendingCssCommandKind::ResolveFrontendNodeForInlineStyle { kind }, pending, - })) + ))) } pub(super) fn backend_node_id_from_frontend_resolution( diff --git a/moli-protocol/src/domains/css/style_sheets.rs b/moli-protocol/src/domains/css/style_sheets.rs index 58dd15efb2..e819cc95e7 100644 --- a/moli-protocol/src/domains/css/style_sheets.rs +++ b/moli-protocol/src/domains/css/style_sheets.rs @@ -32,12 +32,12 @@ pub(super) fn start_pending_enable_command( renderer_inspector_session_id, ) .map_err(PendingCssCommandStartError::renderer_error)?; - Ok(Some(PendingCssCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingCssCommandKind::Enable { frame_id }, + Ok(Some(PendingCssCommandDispatch::from_command( + conn, + cmd, + PendingCssCommandKind::Enable { frame_id }, pending, - })) + ))) } pub(super) fn start_pending_disable_command( @@ -53,12 +53,12 @@ pub(super) fn start_pending_disable_command( let pending = page .start_reset_css_agent_session(renderer_inspector_session_id) .map_err(PendingCssCommandStartError::renderer_error)?; - Ok(Some(PendingCssCommandDispatch { - command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), - kind: PendingCssCommandKind::Disable, + Ok(Some(PendingCssCommandDispatch::from_command( + conn, + cmd, + PendingCssCommandKind::Disable, pending, - })) + ))) } pub(super) fn complete_enable_command_output_plan( diff --git a/moli-protocol/src/domains/dom_debugger.rs b/moli-protocol/src/domains/dom_debugger.rs index 5cd8375c92..7fec6b88af 100644 --- a/moli-protocol/src/domains/dom_debugger.rs +++ b/moli-protocol/src/domains/dom_debugger.rs @@ -6,20 +6,20 @@ use moli_core::page::{ use serde::Deserialize; use serde_json::{Map, Value, json}; -use crate::conn::{CdpConnection, Cmd}; +use crate::conn::{CdpConnection, Cmd, CommandOwnerScope}; use crate::domains::actions::DomDebuggerAction; use crate::domains::command_output::CommandOutputPlan; pub(crate) struct PendingDomDebuggerCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, pending: PendingPageCommand, operation: CompletedDomDebuggerOperation, } pub(crate) struct CompletedDomDebuggerCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, completed: Result, operation: CompletedDomDebuggerOperation, } @@ -78,14 +78,10 @@ fn default_depth() -> i32 { } impl PendingDomDebuggerCommandDispatch { - pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() - } - pub(crate) async fn wait(self) -> CompletedDomDebuggerCommandDispatch { CompletedDomDebuggerCommandDispatch { command_id: self.command_id, - session_id: self.session_id, + owner_scope: self.owner_scope, completed: self.pending.wait().await.map_err(|error| error.to_string()), operation: self.operation, } @@ -98,7 +94,7 @@ impl CompletedDomDebuggerCommandDispatch { } pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() + self.owner_scope.session_id() } } @@ -228,7 +224,7 @@ pub(crate) fn try_start_dom_debugger_command_dispatch( Ok((pending, operation)) => { DomDebuggerCommandTaskStep::Pending(PendingDomDebuggerCommandDispatch { command_id: cmd.id, - session_id: cmd.session_id.map(str::to_owned), + owner_scope: CommandOwnerScope::capture(conn, cmd.session_id), pending, operation, }) @@ -247,15 +243,19 @@ pub(crate) fn complete_pending_dom_debugger_command( conn: &mut CdpConnection, completed: CompletedDomDebuggerCommandDispatch, ) -> CommandOutputPlan { - let session_id = completed.session_id; + let session_id = completed.owner_scope.session_id().map(str::to_owned); + let owner_route = completed.owner_scope.session_owner_route(); match completed.operation { CompletedDomDebuggerOperation::GetEventListeners => { let resolution = completed.completed.and_then(|completion| { - conn.loaded_page_mut_for_protocol_access(session_id.as_deref()) - .and_then(|page| { - page.finish_dom_debugger_get_event_listeners(completion) - .map_err(|error| error.to_string()) - }) + conn.loaded_page_mut_for_protocol_access_for_route( + session_id.as_deref(), + owner_route, + ) + .and_then(|page| { + page.finish_dom_debugger_get_event_listeners(completion) + .map_err(|error| error.to_string()) + }) }); match resolution { Ok(RendererDomDebuggerEventListenersResolution::Found(listeners)) => { @@ -272,14 +272,17 @@ pub(crate) fn complete_pending_dom_debugger_command( enabled, } => { let completion = completed.completed.and_then(|completion| { - conn.loaded_page_mut_for_protocol_access(session_id.as_deref()) - .and_then(|page| { - page.finish_unit_runtime_page_command( - completion, - "DOMDebugger event listener breakpoint", - ) - .map_err(|error| error.to_string()) - }) + conn.loaded_page_mut_for_protocol_access_for_route( + session_id.as_deref(), + owner_route, + ) + .and_then(|page| { + page.finish_unit_runtime_page_command( + completion, + "DOMDebugger event listener breakpoint", + ) + .map_err(|error| error.to_string()) + }) }); if let Err(message) = completion { return CommandOutputPlan::error(-32000, message); @@ -308,14 +311,14 @@ pub(crate) fn complete_pending_dom_debugger_command( enabled, } => { let completion = completed.completed.and_then(|completion| { - conn.loaded_page_mut_for_protocol_access(session_id.as_deref()) - .and_then(|page| { - page.finish_unit_runtime_page_command( - completion, - "DOMDebugger XHR breakpoint", - ) + conn.loaded_page_mut_for_protocol_access_for_route( + session_id.as_deref(), + owner_route, + ) + .and_then(|page| { + page.finish_unit_runtime_page_command(completion, "DOMDebugger XHR breakpoint") .map_err(|error| error.to_string()) - }) + }) }); if let Err(message) = completion { return CommandOutputPlan::error(-32000, message); @@ -337,11 +340,14 @@ pub(crate) fn complete_pending_dom_debugger_command( } CompletedDomDebuggerOperation::ConfigureDomBreakpoint => { let resolution = completed.completed.and_then(|completion| { - conn.loaded_page_mut_for_protocol_access(session_id.as_deref()) - .and_then(|page| { - page.finish_dom_debugger_configure_dom_breakpoint(completion) - .map_err(|error| error.to_string()) - }) + conn.loaded_page_mut_for_protocol_access_for_route( + session_id.as_deref(), + owner_route, + ) + .and_then(|page| { + page.finish_dom_debugger_configure_dom_breakpoint(completion) + .map_err(|error| error.to_string()) + }) }); match resolution { Ok(RendererDomDebuggerDomBreakpointResolution::Configured) => { diff --git a/moli-protocol/src/domains/dom_snapshot.rs b/moli-protocol/src/domains/dom_snapshot.rs index a1fef064e3..9072fa719a 100644 --- a/moli-protocol/src/domains/dom_snapshot.rs +++ b/moli-protocol/src/domains/dom_snapshot.rs @@ -9,19 +9,19 @@ use moli_core::page::{ }; use serde::Deserialize; -use crate::conn::{CdpConnection, Cmd}; +use crate::conn::{CdpConnection, Cmd, CommandOwnerScope}; use crate::domains::actions::DomSnapshotAction; use crate::domains::command_output::CommandOutputPlan; pub(crate) struct PendingDomSnapshotCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, pending: PendingPageCommand, } pub(crate) struct CompletedDomSnapshotCommandDispatch { command_id: Option, - session_id: Option, + owner_scope: CommandOwnerScope, completed: Result, } @@ -31,14 +31,10 @@ pub(crate) enum DomSnapshotCommandDispatchStep { } impl PendingDomSnapshotCommandDispatch { - pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() - } - pub async fn wait(self) -> CompletedDomSnapshotCommandDispatch { CompletedDomSnapshotCommandDispatch { command_id: self.command_id, - session_id: self.session_id, + owner_scope: self.owner_scope, completed: self.pending.wait().await.map_err(|error| error.to_string()), } } @@ -50,7 +46,7 @@ impl CompletedDomSnapshotCommandDispatch { } pub(crate) fn session_id(&self) -> Option<&str> { - self.session_id.as_deref() + self.owner_scope.session_id() } } @@ -143,7 +139,7 @@ fn start_capture_snapshot_command_with_params( DomSnapshotCommandDispatchStep::Pending(PendingDomSnapshotCommandDispatch { command_id, - session_id: session_id.map(str::to_owned), + owner_scope: CommandOwnerScope::capture(conn, session_id), pending, }) } @@ -152,8 +148,12 @@ pub(crate) fn complete_pending_dom_snapshot_command( conn: &mut CdpConnection, completed: CompletedDomSnapshotCommandDispatch, ) -> DomSnapshotCommandDispatchStep { - let session_id = completed.session_id.as_deref(); - let Some(page) = loaded_page_mut_for_session(conn, session_id) else { + let session_id = completed.owner_scope.session_id(); + let owner_route = completed.owner_scope.session_owner_route(); + let Some(page) = conn + .loaded_page_mut_for_protocol_access_for_route(session_id, owner_route) + .ok() + else { return DomSnapshotCommandDispatchStep::Complete(CommandOutputPlan::error( -32000, "NoDocumentLoaded", diff --git a/moli-protocol/src/domains/dom_storage/commands.rs b/moli-protocol/src/domains/dom_storage/commands.rs index aba6ec592f..b683f2be3d 100644 --- a/moli-protocol/src/domains/dom_storage/commands.rs +++ b/moli-protocol/src/domains/dom_storage/commands.rs @@ -6,7 +6,9 @@ use serde_json::json; use url::Url; use crate::{ - conn::{BrowserContextPageStorageHandles, CdpConnection, Cmd, CommandOwnerScope}, + conn::{ + BrowserContextPageStorageHandles, CdpConnection, CdpSessionRoute, Cmd, CommandOwnerScope, + }, domains::{actions::DomStorageAction, command_output::CommandOutputPlan}, }; @@ -199,7 +201,12 @@ fn start_storage_operation( storage_id: DomStorageId, operation: DomStorageOperation, ) -> DomStorageCommandTaskStep { - if let Err(plan) = storage_handles_for_session_owner(conn, cmd.session_id) { + let owner_scope = CommandOwnerScope::capture(conn, cmd.session_id); + if let Err(plan) = storage_handles_for_route( + conn, + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) { return DomStorageCommandTaskStep::Complete(plan); } @@ -210,14 +217,17 @@ fn start_storage_operation( return DomStorageCommandTaskStep::Complete(complete_storage_operation( conn, cmd.session_id, + owner_scope.session_owner_route(), storage_id, operation, &storage_key, )); } - let owner_scope = CommandOwnerScope::capture(conn, cmd.session_id); - if let Ok(page) = conn.loaded_page_mut_for_protocol_access(cmd.session_id) { + if let Ok(page) = conn.loaded_page_mut_for_protocol_access_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) { return match page.start_document_storage_key_snapshot() { Ok(pending) => { DomStorageCommandTaskStep::Pending(Box::new(PendingDomStorageCommandDispatch { @@ -248,6 +258,7 @@ fn start_storage_operation( DomStorageCommandTaskStep::Complete(complete_storage_operation( conn, cmd.session_id, + owner_scope.session_owner_route(), storage_id, operation, &storage_key, @@ -301,12 +312,11 @@ fn complete_top_frame_resolution( return DomStorageCommandTaskStep::Complete(CommandOutputPlan::error(-32000, error)); } }; - let mut route_scope = owner_scope.enter(conn); let storage_key = { - let Ok(page) = route_scope - .conn_mut() - .loaded_page_mut_for_protocol_access(owner_scope.session_id()) - else { + let Ok(page) = conn.loaded_page_mut_for_protocol_access_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) else { return DomStorageCommandTaskStep::Complete(frame_not_found_plan()); }; match page.finish_document_storage_key_snapshot(completion) { @@ -322,8 +332,9 @@ fn complete_top_frame_resolution( if storage_id_matches_key(&storage_id, &storage_key) { return DomStorageCommandTaskStep::Complete(complete_storage_operation( - route_scope.conn_mut(), + conn, owner_scope.session_id(), + owner_scope.session_owner_route(), storage_id, operation, &storage_key, @@ -331,15 +342,14 @@ fn complete_top_frame_resolution( } let child_pending = { - let Ok(page) = route_scope - .conn_mut() - .loaded_page_mut_for_protocol_access(owner_scope.session_id()) - else { + let Ok(page) = conn.loaded_page_mut_for_protocol_access_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) else { return DomStorageCommandTaskStep::Complete(frame_not_found_plan()); }; page.start_child_frame_tree_snapshot() }; - drop(route_scope); match child_pending { Ok(pending) => { DomStorageCommandTaskStep::Pending(Box::new(PendingDomStorageCommandDispatch { @@ -372,12 +382,11 @@ fn complete_child_frame_resolution( return DomStorageCommandTaskStep::Complete(CommandOutputPlan::error(-32000, error)); } }; - let mut route_scope = owner_scope.enter(conn); let child_frames = { - let Ok(page) = route_scope - .conn_mut() - .loaded_page_mut_for_protocol_access(owner_scope.session_id()) - else { + let Ok(page) = conn.loaded_page_mut_for_protocol_access_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) else { return DomStorageCommandTaskStep::Complete(frame_not_found_plan()); }; match page.finish_child_frame_tree_snapshot(completion) { @@ -394,8 +403,9 @@ fn complete_child_frame_resolution( return DomStorageCommandTaskStep::Complete(frame_not_found_plan()); }; DomStorageCommandTaskStep::Complete(complete_storage_operation( - route_scope.conn_mut(), + conn, owner_scope.session_id(), + owner_scope.session_owner_route(), storage_id, operation, &storage_key, @@ -405,6 +415,7 @@ fn complete_child_frame_resolution( fn complete_storage_operation( conn: &CdpConnection, session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, storage_id: DomStorageId, operation: DomStorageOperation, storage_key: &str, @@ -412,7 +423,7 @@ fn complete_storage_operation( if !storage_id_matches_key(&storage_id, storage_key) { return frame_not_found_plan(); } - let handles = match storage_handles_for_session_owner(conn, session_id) { + let handles = match storage_handles_for_route(conn, session_id, owner_route) { Ok(handles) => handles, Err(plan) => return plan, }; @@ -458,9 +469,21 @@ where fn storage_handles_for_session_owner( conn: &CdpConnection, session_id: Option<&str>, +) -> Result { + let none_session_owner_route = session_id + .is_none() + .then(|| conn.none_session_owner_route_override()) + .flatten(); + storage_handles_for_route(conn, session_id, none_session_owner_route.as_ref()) +} + +fn storage_handles_for_route( + conn: &CdpConnection, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, ) -> Result { let Some((browser_context_id, Some(target_id))) = - conn.target_owner_identity_for_session(session_id) + conn.target_owner_identity_for_route(session_id, owner_route) else { return Err(CommandOutputPlan::error( -32000, diff --git a/moli-protocol/src/domains/emulation.rs b/moli-protocol/src/domains/emulation.rs index 350eca88e1..6684369bae 100644 --- a/moli-protocol/src/domains/emulation.rs +++ b/moli-protocol/src/domains/emulation.rs @@ -2832,18 +2832,18 @@ fn finish_pending_emulation_page_command( ) -> Result<(), String> { match target { PendingEmulationPageTarget::SessionOwner { owner_scope } => { - let mut route_scope = owner_scope.enter(conn); if matches!(operation, PendingEmulationPageOperation::SetUserAgentLoader) { - return route_scope - .conn_mut() - .finish_rebuild_resource_runtime_for_session_owner( - owner_scope.session_id(), - completion, - ); + return conn.finish_rebuild_resource_runtime_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + completion, + ); } - let page = route_scope - .conn_mut() - .loaded_page_mut_for_interruptible_protocol_access(owner_scope.session_id()) + let page = conn + .loaded_page_mut_for_interruptible_protocol_access_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) .ok(); finish_emulation_page_operation_on_current_attachment(page, operation, completion) } diff --git a/moli-protocol/src/domains/page.rs b/moli-protocol/src/domains/page.rs index 6e262bb292..046ed78314 100644 --- a/moli-protocol/src/domains/page.rs +++ b/moli-protocol/src/domains/page.rs @@ -1876,10 +1876,10 @@ impl CdpConnection { &mut self, registration: &PageScreencastRegistration, ) -> PageScreencastSubscriptionStatus { - let mut route_scope = registration.owner_scope.enter(self); - page_screencast_subscription_status_for_current_route( - route_scope.conn_mut(), + page_screencast_subscription_status_for_route( + self, registration.session_id(), + registration.owner_scope.session_owner_route(), registration.generation, ) } @@ -1892,22 +1892,25 @@ impl CdpConnection { let session_id = registration.session_id().map(str::to_owned); let generation = registration.generation; let owner_scope = registration.owner_scope.clone(); - let mut route_scope = owner_scope.enter(self); - let conn = route_scope.conn_mut(); let session_id_ref = session_id.as_deref(); - if page_screencast_subscription_status_for_current_route(conn, session_id_ref, generation) - != PageScreencastSubscriptionStatus::Ready + let owner_route = owner_scope.session_owner_route(); + if page_screencast_subscription_status_for_route( + self, + session_id_ref, + owner_route, + generation, + ) != PageScreencastSubscriptionStatus::Ready { return PageScreencastCaptureStart::Stale; } - let Some(config) = conn - .target_page_session_state_for_session(session_id_ref) + let Some(config) = self + .target_page_session_state_for_route(session_id_ref, owner_route) .and_then(|state| state.page_screencast.config()) .cloned() else { return PageScreencastCaptureStart::Stale; }; - let viewport = current_viewport_surface(conn, session_id_ref); + let viewport = current_viewport_surface_for_route(self, session_id_ref, owner_route); let request = RendererCaptureScreencastFrameRequest { format: match config.format() { PageScreencastFormat::Png => RendererScreenshotFormat::Png, @@ -1919,17 +1922,18 @@ impl CdpConnection { max_height: config.max_height(), known_visual_state, }; - let pending = match conn.loaded_page_mut_for_protocol_access(session_id_ref) { - Ok(page) => match page.start_capture_screencast_frame(request) { - Ok(pending) => pending, - Err(error) => { - tracing::debug!(?error, "failed to start screencast frame capture"); - return PageScreencastCaptureStart::Retry; - } - }, - Err(_) => return PageScreencastCaptureStart::Retry, - }; - if conn.begin_page_screencast_capture_for_session_owner(session_id_ref, generation) + let pending = + match self.loaded_page_mut_for_protocol_access_for_route(session_id_ref, owner_route) { + Ok(page) => match page.start_capture_screencast_frame(request) { + Ok(pending) => pending, + Err(error) => { + tracing::debug!(?error, "failed to start screencast frame capture"); + return PageScreencastCaptureStart::Retry; + } + }, + Err(_) => return PageScreencastCaptureStart::Retry, + }; + if self.begin_page_screencast_capture_for_route(session_id_ref, owner_route, generation) != Some(true) { tracing::debug!( @@ -1959,22 +1963,28 @@ impl CdpConnection { viewport, completed, } = completed; - let mut route_scope = owner_scope.enter(self); - let conn = route_scope.conn_mut(); let session_id_ref = session_id.as_deref(); - if page_screencast_subscription_status_for_current_route(conn, session_id_ref, generation) - != PageScreencastSubscriptionStatus::CaptureInProgress + let owner_route = owner_scope.session_owner_route(); + if page_screencast_subscription_status_for_route( + self, + session_id_ref, + owner_route, + generation, + ) != PageScreencastSubscriptionStatus::CaptureInProgress { return PageScreencastCaptureCompletion::Stale; } let frame = match completed { Ok(completion) => { - let page = match conn.loaded_page_mut_for_protocol_access(session_id_ref) { + let page = match self + .loaded_page_mut_for_protocol_access_for_route(session_id_ref, owner_route) + { Ok(page) => page, Err(_) => { - let _ = conn.complete_page_screencast_capture_for_session_owner( + let _ = self.complete_page_screencast_capture_for_route( session_id_ref, + owner_route, generation, false, ); @@ -1984,8 +1994,9 @@ impl CdpConnection { match page.finish_capture_screencast_frame(*completion) { Ok(RendererCaptureScreencastFrameReply::Captured(frame)) => frame, Ok(RendererCaptureScreencastFrameReply::Unchanged) => { - if conn.complete_page_screencast_capture_for_session_owner( + if self.complete_page_screencast_capture_for_route( session_id_ref, + owner_route, generation, false, ) != Some(true) @@ -1999,8 +2010,9 @@ impl CdpConnection { | RendererCaptureScreencastFrameReply::NoDocument, ) | Err(_) => { - let _ = conn.complete_page_screencast_capture_for_session_owner( + let _ = self.complete_page_screencast_capture_for_route( session_id_ref, + owner_route, generation, false, ); @@ -2009,8 +2021,9 @@ impl CdpConnection { } } Err(_) => { - let _ = conn.complete_page_screencast_capture_for_session_owner( + let _ = self.complete_page_screencast_capture_for_route( session_id_ref, + owner_route, generation, false, ); @@ -2019,8 +2032,12 @@ impl CdpConnection { }; let visual_state = frame.visual_state; - if conn.complete_page_screencast_capture_for_session_owner(session_id_ref, generation, true) - != Some(true) + if self.complete_page_screencast_capture_for_route( + session_id_ref, + owner_route, + generation, + true, + ) != Some(true) { return PageScreencastCaptureCompletion::Stale; } @@ -2048,12 +2065,13 @@ impl CdpConnection { } } -fn page_screencast_subscription_status_for_current_route( +fn page_screencast_subscription_status_for_route( conn: &CdpConnection, session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, generation: i32, ) -> PageScreencastSubscriptionStatus { - let Some(state) = conn.target_page_session_state_for_session(session_id) else { + let Some(state) = conn.target_page_session_state_for_route(session_id, owner_route) else { return PageScreencastSubscriptionStatus::Inactive; }; let screencast = &state.page_screencast; @@ -5783,6 +5801,17 @@ fn current_viewport_surface( ) } +fn current_viewport_surface_for_route( + conn: &CdpConnection, + session_id: Option<&str>, + owner_route: Option<&CdpSessionRoute>, +) -> EmulatedViewportSurface { + EmulatedViewportSurface::from_metrics( + conn.target_session_owner_emulated_device_metrics_for_route(session_id, owner_route) + .as_ref(), + ) +} + async fn execute_devtools_get_layout_metrics_command( conn: &mut CdpConnection, command: DevToolsGetLayoutMetricsCommand, diff --git a/moli-protocol/src/domains/page/termination.rs b/moli-protocol/src/domains/page/termination.rs index f582c9ca10..47853353c0 100644 --- a/moli-protocol/src/domains/page/termination.rs +++ b/moli-protocol/src/domains/page/termination.rs @@ -647,11 +647,12 @@ pub(crate) async fn complete_page_target_termination_owner_action_async( action: PageTargetTerminationOwnerAction, ) -> crate::conn::CdpTurnOutcome { let (owner_scope, expected_target_id, kind) = action.into_parts(); - let mut route_scope = owner_scope.enter(conn); - let conn = route_scope.conn_mut(); let mut out = Vec::new(); let current_target_id = conn - .target_owner_identity_for_session(owner_scope.session_id()) + .target_owner_identity_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) .and_then(|(_, target_id)| target_id); if current_target_id.as_deref() != Some(expected_target_id.as_str()) { return crate::conn::CdpTurnOutcome::new_with_protocol_events( @@ -662,8 +663,11 @@ pub(crate) async fn complete_page_target_termination_owner_action_async( let target_host_closure = conn.prepare_target_host_closure(&expected_target_id); let closed = match kind { PageTargetTerminationKind::PageClose => { - conn.close_page_target_for_session_owner_async(owner_scope.session_id()) - .await + conn.close_page_target_for_route_async( + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) + .await } PageTargetTerminationKind::TargetClose => { let is_active_target = conn diff --git a/moli-protocol/src/domains/page/tests/lifecycle.rs b/moli-protocol/src/domains/page/tests/lifecycle.rs index ae440e2c18..2a3e16c091 100644 --- a/moli-protocol/src/domains/page/tests/lifecycle.rs +++ b/moli-protocol/src/domains/page/tests/lifecycle.rs @@ -3640,15 +3640,12 @@ async fn screencast_commands_update_page_session_state() { assert_eq!( ctx.conn - .begin_page_screencast_capture_for_session_owner(Some("SID-screencast"), 1), + .begin_page_screencast_capture_for_route(Some("SID-screencast"), None, 1), Some(true) ); assert_eq!( - ctx.conn.complete_page_screencast_capture_for_session_owner( - Some("SID-screencast"), - 1, - true, - ), + ctx.conn + .complete_page_screencast_capture_for_route(Some("SID-screencast"), None, 1, true,), Some(true) ); @@ -4125,15 +4122,17 @@ async fn repeated_start_invalidates_old_screencast_ack_generation() { let generation = i32::try_from(id - 62).expect("small screencast generation"); assert_eq!( - ctx.conn.begin_page_screencast_capture_for_session_owner( + ctx.conn.begin_page_screencast_capture_for_route( Some("SID-screencast-generation"), + None, generation, ), Some(true) ); assert_eq!( - ctx.conn.complete_page_screencast_capture_for_session_owner( + ctx.conn.complete_page_screencast_capture_for_route( Some("SID-screencast-generation"), + None, generation, true, ), diff --git a/moli-protocol/src/domains/performance.rs b/moli-protocol/src/domains/performance.rs index becdbec340..53de79387f 100644 --- a/moli-protocol/src/domains/performance.rs +++ b/moli-protocol/src/domains/performance.rs @@ -1,6 +1,7 @@ use crate::conn::{ - CdpConnection, CdpRendererCommandAccess, Cmd, CommandOwnerScope, PerformanceTimeDomain, - RendererCommandCorrelation, RendererCommandDescriptor, monotonic_timestamp_seconds, + CdpConnection, CdpRendererCommandAccess, CdpSessionRoute, Cmd, CommandOwnerScope, + PerformanceTimeDomain, RendererCommandCorrelation, RendererCommandDescriptor, + monotonic_timestamp_seconds, }; use crate::domains::actions::PerformanceAction; use crate::domains::command_output::CommandOutputPlan; @@ -66,10 +67,6 @@ pub(crate) enum PerformanceCommandTaskStep { } impl PendingPerformanceCommandDispatch { - pub(crate) fn session_id(&self) -> Option<&str> { - self.owner_scope.session_id() - } - pub async fn wait(self) -> CompletedPerformanceCommandDispatch { let completed = match *self.pending { PendingPerformanceRendererCommand::Main(pending) => pending @@ -119,14 +116,14 @@ fn loaded_page_mut_for_renderer_access<'a>( conn: &'a mut CdpConnection, session_id: Option<&str>, renderer_access: CdpRendererCommandAccess, + owner_route: Option<&CdpSessionRoute>, ) -> Result<&'a mut Page, String> { match renderer_access { CdpRendererCommandAccess::MainThread => { - conn.loaded_page_mut_for_protocol_access(session_id) - } - CdpRendererCommandAccess::Io => { - conn.loaded_page_mut_for_interruptible_protocol_access(session_id) + conn.loaded_page_mut_for_protocol_access_for_route(session_id, owner_route) } + CdpRendererCommandAccess::Io => conn + .loaded_page_mut_for_interruptible_protocol_access_for_route(session_id, owner_route), CdpRendererCommandAccess::OwnerIndependent => { Err("Performance.getMetrics requires a renderer Page".to_owned()) } @@ -239,15 +236,19 @@ pub(crate) fn try_start_performance_command_dispatch( let owner_scope = CommandOwnerScope::capture(conn, cmd.session_id); if renderer_access == CdpRendererCommandAccess::Io { let (renderer_page, attachment_id, snapshot) = { - let page = - match loaded_page_mut_for_renderer_access(conn, cmd.session_id, renderer_access) { - Ok(page) => page, - Err(_) => { - return PerformanceCommandTaskStep::Complete( - default_metrics_command_output_plan(), - ); - } - }; + let page = match loaded_page_mut_for_renderer_access( + conn, + cmd.session_id, + renderer_access, + owner_scope.session_owner_route(), + ) { + Ok(page) => page, + Err(_) => { + return PerformanceCommandTaskStep::Complete( + default_metrics_command_output_plan(), + ); + } + }; ( crate::conn::RendererPageResidenceIdentity::from_page(page), page.renderer_agent_attachment_id(), @@ -260,8 +261,13 @@ pub(crate) fn try_start_performance_command_dispatch( if cmd.id.is_none() || response_delivery == moli_page_types::RendererInspectorResponseDelivery::CommandReply { - let page = loaded_page_mut_for_renderer_access(conn, cmd.session_id, renderer_access) - .expect("the captured Performance Page must remain loaded synchronously"); + let page = loaded_page_mut_for_renderer_access( + conn, + cmd.session_id, + renderer_access, + owner_scope.session_owner_route(), + ) + .expect("the captured Performance Page must remain loaded synchronously"); let (pending, snapshot) = page.start_performance_metric_snapshot_from_io(); return PerformanceCommandTaskStep::Pending(PendingPerformanceCommandDispatch { command_id: cmd.id, @@ -306,21 +312,26 @@ pub(crate) fn try_start_performance_command_dispatch( response_rx.is_none(), "Performance session output must not allocate a command-reply receiver", ); - let pending = loaded_page_mut_for_renderer_access(conn, cmd.session_id, renderer_access) - .ok() - .filter(|page| { - crate::conn::RendererPageResidenceIdentity::from_page(page) == renderer_page - && page.renderer_agent_attachment_id() == Some(attachment_id) - }) - .ok_or_else(|| "Performance renderer attachment changed before IO dispatch".to_owned()) - .and_then(|page| { - page.start_performance_get_metrics_from_io_with_response( - renderer_inspector_session_id, - result, - response, - ) - .map_err(|error| error.to_string()) - }); + let pending = loaded_page_mut_for_renderer_access( + conn, + cmd.session_id, + renderer_access, + owner_scope.session_owner_route(), + ) + .ok() + .filter(|page| { + crate::conn::RendererPageResidenceIdentity::from_page(page) == renderer_page + && page.renderer_agent_attachment_id() == Some(attachment_id) + }) + .ok_or_else(|| "Performance renderer attachment changed before IO dispatch".to_owned()) + .and_then(|page| { + page.start_performance_get_metrics_from_io_with_response( + renderer_inspector_session_id, + result, + response, + ) + .map_err(|error| error.to_string()) + }); let pending = match pending { Ok(pending) => pending, Err(error) => { @@ -345,7 +356,12 @@ pub(crate) fn try_start_performance_command_dispatch( }), }); } - let page = match loaded_page_mut_for_renderer_access(conn, cmd.session_id, renderer_access) { + let page = match loaded_page_mut_for_renderer_access( + conn, + cmd.session_id, + renderer_access, + owner_scope.session_owner_route(), + ) { Ok(page) => page, Err(_) => { return PerformanceCommandTaskStep::Complete(default_metrics_command_output_plan()); @@ -397,13 +413,13 @@ pub(crate) async fn complete_pending_performance_command( completed, } = completed; let session_id = owner_scope.session_id().map(str::to_owned); - let mut owner_scope = owner_scope.enter(conn); let snapshot = match completed { Ok(CompletedPerformanceRendererCommand::Main(completed_page)) => { loaded_page_mut_for_renderer_access( - owner_scope.conn_mut(), + conn, session_id.as_deref(), renderer_access, + owner_scope.session_owner_route(), ) .ok() .filter(|page| { @@ -414,9 +430,10 @@ pub(crate) async fn complete_pending_performance_command( } Ok(CompletedPerformanceRendererCommand::IoCommandReply(snapshot)) => { let remains_current = loaded_page_mut_for_renderer_access( - owner_scope.conn_mut(), + conn, session_id.as_deref(), renderer_access, + owner_scope.session_owner_route(), ) .ok() .is_some_and(|page| { @@ -439,13 +456,11 @@ pub(crate) async fn complete_pending_performance_command( plan.set_renderer_output_predecessor(predecessor); return plan; } - if !owner_scope - .conn_mut() - .take_renderer_call_if_correlation_matches_for_session_owner( - session_id.as_deref(), - correlation, - ) - { + if !conn.take_renderer_call_if_correlation_matches_for_route( + session_id.as_deref(), + owner_scope.session_owner_route(), + correlation, + ) { return CommandOutputPlan::default(); } RendererPerformanceMetricSnapshot::default() @@ -929,6 +944,80 @@ mod tests { assert!(metrics["Nodes"] >= 4.0); } + #[tokio::test(flavor = "multi_thread")] + async fn pending_performance_metrics_keep_captured_implicit_page_owner() { + let mut ctx = TestContext::new(); + let active_url = "data:text/html,active"; + let background_url = "data:text/html,
"; + let mut browser_context = BrowserContext::new("BID-performance-owner".to_owned()); + browser_context.set_active_target_id("TID-performance-active"); + browser_context.attach_active_session("SID-performance-active"); + browser_context.set_target_url(active_url.to_owned()); + browser_context.stage_background_target( + "TID-performance-background".to_owned(), + Some("SID-performance-background".to_owned()), + background_url.to_owned(), + None, + None, + ); + ctx.conn.browser_context = Some(browser_context); + ctx.install_navigation_fixture_for_session_owner( + active_url, + Some("SID-performance-active"), + ) + .await; + ctx.install_navigation_fixture_for_session_owner( + background_url, + Some("SID-performance-background"), + ) + .await; + + let background_route = ctx + .conn + .target_session_route_for_target_id("TID-performance-background") + .expect("background Performance route"); + let previous_route = ctx + .conn + .replace_none_session_owner_route_override(Some(background_route.clone())); + let enable = ctx + .conn + .start_command_dispatch(r#"{"id":4200,"method":"Performance.enable"}"#); + let enable_messages = complete_immediate_command_task_step_for_test(enable); + ctx.conn + .replace_none_session_owner_route_override(previous_route); + assert_eq!(enable_messages[0]["result"], json!({})); + + let previous_route = ctx + .conn + .replace_none_session_owner_route_override(Some(background_route)); + let pending = ctx + .conn + .start_command_dispatch(r#"{"id":4201,"method":"Performance.getMetrics"}"#); + ctx.conn + .replace_none_session_owner_route_override(previous_route); + assert!(matches!(pending, CdpCommandTaskStep::Pending(_))); + + let active_route = ctx + .conn + .target_session_route_for_target_id("TID-performance-active") + .expect("active Performance route"); + let previous_route = ctx + .conn + .replace_none_session_owner_route_override(Some(active_route)); + let messages = complete_command_task_step_for_test(&mut ctx, pending, 4201).await; + ctx.conn + .replace_none_session_owner_route_override(previous_route); + let response = messages + .iter() + .find(|message| message["id"] == json!(4201)) + .expect("pending background Performance response"); + assert!( + metric_map(response)["Nodes"] >= 8.0, + "completion must finish the snapshot on the captured background Page" + ); + assert_eq!(ctx.conn.none_session_owner_route_override(), None); + } + #[tokio::test(flavor = "multi_thread")] async fn performance_get_metrics_reads_the_snapshot_bound_at_each_replacement() { let mut ctx = TestContext::new(); diff --git a/moli-protocol/src/domains/security.rs b/moli-protocol/src/domains/security.rs index 231a107f63..f4ebca90a6 100644 --- a/moli-protocol/src/domains/security.rs +++ b/moli-protocol/src/domains/security.rs @@ -96,12 +96,12 @@ pub(crate) fn complete_pending_security_command( Ok(completion) => completion, Err(error) => return CommandOutputPlan::error(-32000, error), }; - let owner_scope = completed.owner_scope.clone(); - let mut route_scope = owner_scope.enter(conn); - match route_scope - .conn_mut() - .finish_rebuild_resource_runtime_for_session_owner(owner_scope.session_id(), completion) - { + let owner_scope = completed.owner_scope; + match conn.finish_rebuild_resource_runtime_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + completion, + ) { Ok(()) => CommandOutputPlan::success(), Err(error) => CommandOutputPlan::error(-32000, error), } diff --git a/moli-protocol/src/domains/storage/ops.rs b/moli-protocol/src/domains/storage/ops.rs index de6be03ea0..520b2173ed 100644 --- a/moli-protocol/src/domains/storage/ops.rs +++ b/moli-protocol/src/domains/storage/ops.rs @@ -495,7 +495,9 @@ fn start_get_storage_key_for_frame_command( if params.frame_id == target_id { let owner_scope = CommandOwnerScope::capture(conn, cmd.session_id); - if let Some(page) = loaded_page_mut_for_session(conn, cmd.session_id) { + if let Some(page) = + loaded_page_mut_for_session(conn, cmd.session_id, owner_scope.session_owner_route()) + { return match page.start_document_storage_key_snapshot() { Ok(pending) => StorageCommandTaskStep::Pending(PendingStorageCommandDispatch { command_id: cmd.id, @@ -522,7 +524,9 @@ fn start_get_storage_key_for_frame_command( return StorageCommandTaskStep::Complete(CommandOutputPlan::error(-32000, message)); } let owner_scope = CommandOwnerScope::capture(conn, cmd.session_id); - let Some(page) = loaded_page_mut_for_session(conn, cmd.session_id) else { + let Some(page) = + loaded_page_mut_for_session(conn, cmd.session_id, owner_scope.session_owner_route()) + else { return StorageCommandTaskStep::Complete(CommandOutputPlan::error( -32000, "NoFrameForGivenId", @@ -773,9 +777,11 @@ fn complete_get_storage_key_for_top_frame_command( Ok(completion) => completion, Err(error) => return CommandOutputPlan::error(-32000, error), }; - let mut route_scope = owner_scope.enter(conn); - let Some(page) = loaded_page_mut_for_session(route_scope.conn_mut(), owner_scope.session_id()) - else { + let Some(page) = loaded_page_mut_for_session( + conn, + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) else { return CommandOutputPlan::error(-32000, "NoFrameForGivenId"); }; match page.finish_document_storage_key_snapshot(completion) { @@ -794,9 +800,11 @@ fn complete_get_storage_key_for_frame_command( Ok(completion) => completion, Err(error) => return CommandOutputPlan::error(-32000, error), }; - let mut route_scope = owner_scope.enter(conn); - let Some(page) = loaded_page_mut_for_session(route_scope.conn_mut(), owner_scope.session_id()) - else { + let Some(page) = loaded_page_mut_for_session( + conn, + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) else { return CommandOutputPlan::error(-32000, "NoFrameForGivenId"); }; let child_frames = match page.finish_child_frame_tree_snapshot(completion) { @@ -910,8 +918,10 @@ fn complete_set_cookies_result( fn loaded_page_mut_for_session<'a>( conn: &'a mut CdpConnection, session_id: Option<&str>, + owner_route: Option<&crate::conn::CdpSessionRoute>, ) -> Option<&'a mut moli_core::page::Page> { - conn.loaded_page_mut_for_protocol_access(session_id).ok() + conn.loaded_page_mut_for_protocol_access_for_route(session_id, owner_route) + .ok() } fn find_child_frame<'a>( diff --git a/moli-protocol/src/domains/target/popup.rs b/moli-protocol/src/domains/target/popup.rs index e942a2dea2..d0bea8c19d 100644 --- a/moli-protocol/src/domains/target/popup.rs +++ b/moli-protocol/src/domains/target/popup.rs @@ -566,16 +566,15 @@ pub(crate) async fn complete_popup_target_activation_action_async( action: PopupTargetActivationAction, ) -> crate::conn::CdpTurnOutcome { let (owner_scope, browser_context_id, target_id) = action.into_parts(); - let target_is_current = { - let mut route_scope = owner_scope.enter(conn); - let conn = route_scope.conn_mut(); - conn.target_owner_identity_for_session(None).is_some_and( - |(current_browser_context_id, current_target_id)| { - current_browser_context_id == browser_context_id - && current_target_id.as_deref() == Some(target_id.as_str()) - }, - ) && popup_target_has_loaded_page(conn, &browser_context_id, &target_id) - }; + let target_is_current = + conn.target_owner_identity_for_route( + owner_scope.session_id(), + owner_scope.session_owner_route(), + ) + .is_some_and(|(current_browser_context_id, current_target_id)| { + current_browser_context_id == browser_context_id + && current_target_id.as_deref() == Some(target_id.as_str()) + }) && popup_target_has_loaded_page(conn, &browser_context_id, &target_id); if !target_is_current { tracing::debug!( browser_context_id,