viewpoint_core/page/frame/
navigation.rs1use std::time::Duration;
4
5use tracing::{debug, info, instrument};
6use viewpoint_cdp::protocol::page::NavigateParams;
7
8use super::Frame;
9use crate::error::NavigationError;
10use crate::wait::{DocumentLoadState, LoadStateWaiter};
11
12pub(super) const DEFAULT_NAVIGATION_TIMEOUT: Duration = Duration::from_secs(30);
14
15impl Frame {
16 #[instrument(level = "info", skip(self), fields(frame_id = %self.id, url = %url))]
22 pub async fn goto(&self, url: &str) -> Result<(), NavigationError> {
23 self.goto_with_options(url, DocumentLoadState::Load, DEFAULT_NAVIGATION_TIMEOUT)
24 .await
25 }
26
27 #[instrument(level = "info", skip(self), fields(frame_id = %self.id, url = %url, wait_until = ?wait_until))]
33 pub async fn goto_with_options(
34 &self,
35 url: &str,
36 wait_until: DocumentLoadState,
37 timeout: Duration,
38 ) -> Result<(), NavigationError> {
39 if self.is_detached() {
40 return Err(NavigationError::Cancelled);
41 }
42
43 info!("Navigating frame to URL");
44
45 let event_rx = self.connection.subscribe_events();
47 let mut waiter = LoadStateWaiter::new(event_rx, self.session_id.clone(), self.id.clone());
48
49 debug!("Sending Page.navigate command for frame");
51 let result: viewpoint_cdp::protocol::page::NavigateResult = self
52 .connection
53 .send_command(
54 "Page.navigate",
55 Some(NavigateParams {
56 url: url.to_string(),
57 referrer: None,
58 transition_type: None,
59 frame_id: Some(self.id.clone()),
60 }),
61 Some(&self.session_id),
62 )
63 .await?;
64
65 debug!(frame_id = %result.frame_id, "Page.navigate completed for frame");
66
67 if let Some(error_text) = result.error_text {
69 return Err(NavigationError::NetworkError(error_text));
70 }
71
72 waiter.set_commit_received().await;
74
75 debug!(wait_until = ?wait_until, "Waiting for load state");
77 waiter
78 .wait_for_load_state_with_timeout(wait_until, timeout)
79 .await?;
80
81 self.set_url(url.to_string());
83
84 info!(frame_id = %self.id, "Frame navigation completed");
85 Ok(())
86 }
87
88 #[instrument(level = "debug", skip(self), fields(frame_id = %self.id, state = ?state))]
94 pub async fn wait_for_load_state(
95 &self,
96 state: DocumentLoadState,
97 ) -> Result<(), NavigationError> {
98 self.wait_for_load_state_with_timeout(state, DEFAULT_NAVIGATION_TIMEOUT)
99 .await
100 }
101
102 #[instrument(level = "debug", skip(self), fields(frame_id = %self.id, state = ?state, timeout_ms = timeout.as_millis()))]
108 pub async fn wait_for_load_state_with_timeout(
109 &self,
110 state: DocumentLoadState,
111 timeout: Duration,
112 ) -> Result<(), NavigationError> {
113 if self.is_detached() {
114 return Err(NavigationError::Cancelled);
115 }
116
117 let event_rx = self.connection.subscribe_events();
118 let mut waiter = LoadStateWaiter::new(event_rx, self.session_id.clone(), self.id.clone());
119
120 waiter.set_commit_received().await;
122
123 waiter
124 .wait_for_load_state_with_timeout(state, timeout)
125 .await?;
126
127 debug!("Frame reached load state {:?}", state);
128 Ok(())
129 }
130}