chromiumoxide/
navigate_deadline.rs1use std::time::Duration;
18
19use chromiumoxide_cdp::cdp::browser_protocol::page::{NavigateParams, NavigateReturns};
20use serde::Serialize;
21
22#[derive(Debug, Clone, PartialEq, Serialize)]
27pub struct NavigateWithDeadlineParams {
28 #[serde(flatten)]
29 inner: NavigateParams,
30 timeout: i64,
32}
33
34impl NavigateWithDeadlineParams {
35 pub fn new(params: impl Into<NavigateParams>, navigation_timeout: Duration) -> Self {
41 Self {
42 inner: params.into(),
43 timeout: duration_as_millis_i64(navigation_timeout),
44 }
45 }
46
47 pub fn params(&self) -> &NavigateParams {
49 &self.inner
50 }
51
52 pub fn url(&self) -> &str {
54 &self.inner.url
55 }
56
57 pub fn timeout_millis(&self) -> i64 {
59 self.timeout
60 }
61
62 pub fn into_params(self) -> NavigateParams {
64 self.inner
65 }
66}
67
68#[inline]
70fn duration_as_millis_i64(duration: Duration) -> i64 {
71 i64::try_from(duration.as_millis()).unwrap_or(i64::MAX)
72}
73
74impl chromiumoxide_types::Method for NavigateWithDeadlineParams {
75 fn identifier(&self) -> chromiumoxide_types::MethodId {
76 NavigateParams::IDENTIFIER.into()
77 }
78}
79
80impl chromiumoxide_types::MethodType for NavigateWithDeadlineParams {
81 fn method_id() -> chromiumoxide_types::MethodId
82 where
83 Self: Sized,
84 {
85 NavigateParams::IDENTIFIER.into()
86 }
87}
88
89impl chromiumoxide_types::Command for NavigateWithDeadlineParams {
90 type Response = NavigateReturns;
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use chromiumoxide_types::{Method, MethodType};
97
98 #[test]
99 fn method_string_is_page_navigate() {
100 let params = NavigateWithDeadlineParams::new(
101 NavigateParams::new("https://example.test/"),
102 Duration::from_secs(3),
103 );
104 assert_eq!(params.identifier().as_ref(), "Page.navigate");
105 assert_eq!(
106 NavigateWithDeadlineParams::method_id().as_ref(),
107 NavigateParams::IDENTIFIER
108 );
109 }
110
111 #[test]
112 fn timeout_is_integer_milliseconds() {
113 let params = NavigateWithDeadlineParams::new(
114 NavigateParams::new("https://example.test/"),
115 Duration::from_secs(3),
116 );
117 let value = serde_json::to_value(¶ms).expect("serialize");
118 assert_eq!(value["timeout"], serde_json::json!(3000));
119 assert_eq!(params.timeout_millis(), 3000);
120 }
121
122 #[test]
123 fn oversized_duration_saturates_instead_of_wrapping() {
124 let params = NavigateWithDeadlineParams::new(
125 NavigateParams::new("https://example.test/"),
126 Duration::from_secs(u64::MAX),
127 );
128 assert_eq!(params.timeout_millis(), i64::MAX);
129 let value = serde_json::to_value(¶ms).expect("serialize");
130 assert_eq!(value["timeout"], serde_json::json!(i64::MAX));
131 assert!(
132 value["timeout"].as_i64().is_some_and(|ms| ms > 0),
133 "deadline must never serialize as a negative value"
134 );
135 }
136
137 #[test]
138 fn zero_duration_is_zero_millis() {
139 let params = NavigateWithDeadlineParams::new(
140 NavigateParams::new("https://example.test/"),
141 Duration::ZERO,
142 );
143 assert_eq!(params.timeout_millis(), 0);
144 }
145
146 #[test]
147 fn flatten_keeps_every_standard_field_identical() {
148 let base = NavigateParams {
149 url: "https://example.test/page".into(),
150 referrer: Some("https://referrer.test/".into()),
151 transition_type: Some(
152 chromiumoxide_cdp::cdp::browser_protocol::page::TransitionType::Link,
153 ),
154 frame_id: Some(
155 chromiumoxide_cdp::cdp::browser_protocol::page::FrameId::from(
156 "frame-abc".to_string(),
157 ),
158 ),
159 referrer_policy: Some(
160 chromiumoxide_cdp::cdp::browser_protocol::page::ReferrerPolicy::NoReferrer,
161 ),
162 };
163 let plain = serde_json::to_value(&base).expect("serialize plain");
164 let armed = serde_json::to_value(NavigateWithDeadlineParams::new(
165 base,
166 Duration::from_millis(1500),
167 ))
168 .expect("serialize armed");
169
170 let plain = plain.as_object().expect("plain object");
171 let armed_obj = armed.as_object().expect("armed object");
172
173 for (key, value) in plain {
174 assert_eq!(
175 armed_obj.get(key),
176 Some(value),
177 "key `{key}` diverged on the armed path"
178 );
179 }
180 let mut extra: Vec<&String> = armed_obj
181 .keys()
182 .filter(|k| !plain.contains_key(*k))
183 .collect();
184 extra.sort();
185 assert_eq!(extra, vec![&"timeout".to_string()]);
186 }
187
188 #[test]
189 fn plain_params_carry_no_timeout_key() {
190 let value =
191 serde_json::to_value(NavigateParams::new("https://example.test/")).expect("serialize");
192 let object = value.as_object().expect("object");
193 assert!(!object.contains_key("timeout"));
194 assert_eq!(object.keys().collect::<Vec<_>>(), vec!["url"]);
195 }
196}