use std::time::Duration;
use tracing::{debug, info, instrument};
use viewpoint_cdp::protocol::page::NavigateParams;
use super::Frame;
use crate::error::NavigationError;
use crate::wait::{DocumentLoadState, LoadStateWaiter};
pub(super) const DEFAULT_NAVIGATION_TIMEOUT: Duration = Duration::from_secs(30);
impl Frame {
#[instrument(level = "info", skip(self), fields(frame_id = %self.id, url = %url))]
pub async fn goto(&self, url: &str) -> Result<(), NavigationError> {
self.goto_with_options(url, DocumentLoadState::Load, DEFAULT_NAVIGATION_TIMEOUT)
.await
}
#[instrument(level = "info", skip(self), fields(frame_id = %self.id, url = %url, wait_until = ?wait_until))]
pub async fn goto_with_options(
&self,
url: &str,
wait_until: DocumentLoadState,
timeout: Duration,
) -> Result<(), NavigationError> {
if self.is_detached() {
return Err(NavigationError::Cancelled);
}
info!("Navigating frame to URL");
let event_rx = self.connection.subscribe_events();
let mut waiter = LoadStateWaiter::new(event_rx, self.session_id.clone(), self.id.clone());
debug!("Sending Page.navigate command for frame");
let result: viewpoint_cdp::protocol::page::NavigateResult = self
.connection
.send_command(
"Page.navigate",
Some(NavigateParams {
url: url.to_string(),
referrer: None,
transition_type: None,
frame_id: Some(self.id.clone()),
}),
Some(&self.session_id),
)
.await?;
debug!(frame_id = %result.frame_id, "Page.navigate completed for frame");
if let Some(error_text) = result.error_text {
return Err(NavigationError::NetworkError(error_text));
}
waiter.set_commit_received().await;
debug!(wait_until = ?wait_until, "Waiting for load state");
waiter
.wait_for_load_state_with_timeout(wait_until, timeout)
.await?;
self.set_url(url.to_string());
info!(frame_id = %self.id, "Frame navigation completed");
Ok(())
}
#[instrument(level = "debug", skip(self), fields(frame_id = %self.id, state = ?state))]
pub async fn wait_for_load_state(
&self,
state: DocumentLoadState,
) -> Result<(), NavigationError> {
self.wait_for_load_state_with_timeout(state, DEFAULT_NAVIGATION_TIMEOUT)
.await
}
#[instrument(level = "debug", skip(self), fields(frame_id = %self.id, state = ?state, timeout_ms = timeout.as_millis()))]
pub async fn wait_for_load_state_with_timeout(
&self,
state: DocumentLoadState,
timeout: Duration,
) -> Result<(), NavigationError> {
if self.is_detached() {
return Err(NavigationError::Cancelled);
}
let event_rx = self.connection.subscribe_events();
let mut waiter = LoadStateWaiter::new(event_rx, self.session_id.clone(), self.id.clone());
waiter.set_commit_received().await;
waiter
.wait_for_load_state_with_timeout(state, timeout)
.await?;
debug!("Frame reached load state {:?}", state);
Ok(())
}
}