use std::time::Duration;
use chromiumoxide_cdp::cdp::browser_protocol::page::{NavigateParams, NavigateReturns};
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct NavigateWithDeadlineParams {
#[serde(flatten)]
inner: NavigateParams,
timeout: i64,
}
impl NavigateWithDeadlineParams {
pub fn new(params: impl Into<NavigateParams>, navigation_timeout: Duration) -> Self {
Self {
inner: params.into(),
timeout: duration_as_millis_i64(navigation_timeout),
}
}
pub fn params(&self) -> &NavigateParams {
&self.inner
}
pub fn url(&self) -> &str {
&self.inner.url
}
pub fn timeout_millis(&self) -> i64 {
self.timeout
}
pub fn into_params(self) -> NavigateParams {
self.inner
}
}
#[inline]
fn duration_as_millis_i64(duration: Duration) -> i64 {
i64::try_from(duration.as_millis()).unwrap_or(i64::MAX)
}
impl chromiumoxide_types::Method for NavigateWithDeadlineParams {
fn identifier(&self) -> chromiumoxide_types::MethodId {
NavigateParams::IDENTIFIER.into()
}
}
impl chromiumoxide_types::MethodType for NavigateWithDeadlineParams {
fn method_id() -> chromiumoxide_types::MethodId
where
Self: Sized,
{
NavigateParams::IDENTIFIER.into()
}
}
impl chromiumoxide_types::Command for NavigateWithDeadlineParams {
type Response = NavigateReturns;
}
#[cfg(test)]
mod tests {
use super::*;
use chromiumoxide_types::{Method, MethodType};
#[test]
fn method_string_is_page_navigate() {
let params = NavigateWithDeadlineParams::new(
NavigateParams::new("https://example.test/"),
Duration::from_secs(3),
);
assert_eq!(params.identifier().as_ref(), "Page.navigate");
assert_eq!(
NavigateWithDeadlineParams::method_id().as_ref(),
NavigateParams::IDENTIFIER
);
}
#[test]
fn timeout_is_integer_milliseconds() {
let params = NavigateWithDeadlineParams::new(
NavigateParams::new("https://example.test/"),
Duration::from_secs(3),
);
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["timeout"], serde_json::json!(3000));
assert_eq!(params.timeout_millis(), 3000);
}
#[test]
fn oversized_duration_saturates_instead_of_wrapping() {
let params = NavigateWithDeadlineParams::new(
NavigateParams::new("https://example.test/"),
Duration::from_secs(u64::MAX),
);
assert_eq!(params.timeout_millis(), i64::MAX);
let value = serde_json::to_value(¶ms).expect("serialize");
assert_eq!(value["timeout"], serde_json::json!(i64::MAX));
assert!(
value["timeout"].as_i64().is_some_and(|ms| ms > 0),
"deadline must never serialize as a negative value"
);
}
#[test]
fn zero_duration_is_zero_millis() {
let params = NavigateWithDeadlineParams::new(
NavigateParams::new("https://example.test/"),
Duration::ZERO,
);
assert_eq!(params.timeout_millis(), 0);
}
#[test]
fn flatten_keeps_every_standard_field_identical() {
let base = NavigateParams {
url: "https://example.test/page".into(),
referrer: Some("https://referrer.test/".into()),
transition_type: Some(
chromiumoxide_cdp::cdp::browser_protocol::page::TransitionType::Link,
),
frame_id: Some(
chromiumoxide_cdp::cdp::browser_protocol::page::FrameId::from(
"frame-abc".to_string(),
),
),
referrer_policy: Some(
chromiumoxide_cdp::cdp::browser_protocol::page::ReferrerPolicy::NoReferrer,
),
};
let plain = serde_json::to_value(&base).expect("serialize plain");
let armed = serde_json::to_value(NavigateWithDeadlineParams::new(
base,
Duration::from_millis(1500),
))
.expect("serialize armed");
let plain = plain.as_object().expect("plain object");
let armed_obj = armed.as_object().expect("armed object");
for (key, value) in plain {
assert_eq!(
armed_obj.get(key),
Some(value),
"key `{key}` diverged on the armed path"
);
}
let mut extra: Vec<&String> = armed_obj
.keys()
.filter(|k| !plain.contains_key(*k))
.collect();
extra.sort();
assert_eq!(extra, vec![&"timeout".to_string()]);
}
#[test]
fn plain_params_carry_no_timeout_key() {
let value =
serde_json::to_value(NavigateParams::new("https://example.test/")).expect("serialize");
let object = value.as_object().expect("object");
assert!(!object.contains_key("timeout"));
assert_eq!(object.keys().collect::<Vec<_>>(), vec!["url"]);
}
}