1use std::time::Duration;
14
15use serde_json::json;
16use tokio::time::Instant;
17
18use crate::browser::interceptor::InterceptedRequest;
19use crate::browser::listener::DataPacket;
20use crate::browser::tab::{CookieParam, ListenStream, LoadMode, Tab};
21use crate::{Error, Result};
22
23pub struct Wait {
25 tab: Tab,
26}
27
28impl Wait {
29 pub(crate) fn new(tab: Tab) -> Self {
30 Self { tab }
31 }
32
33 fn timeout_or_default(&self, t: Option<Duration>) -> Duration {
34 t.unwrap_or_else(|| self.tab.core.timeout())
35 }
36
37 pub async fn secs(&self, secs: f64) {
39 tokio::time::sleep(Duration::from_secs_f64(secs.max(0.0))).await;
40 }
41
42 pub async fn doc_loaded(&self, timeout: Option<Duration>) -> Result<bool> {
44 let d = self.timeout_or_default(timeout);
45 self.tab.core.poll_ready_complete(d).await
46 }
47
48 pub async fn ele_loaded(&self, selector: &str, timeout: Option<Duration>) -> Result<bool> {
50 let deadline = Instant::now() + self.timeout_or_default(timeout);
51 loop {
52 if self.tab.find_once(selector).await?.is_some() {
53 return Ok(true);
54 }
55 if Instant::now() >= deadline {
56 return Ok(false);
57 }
58 tokio::time::sleep(Duration::from_millis(100)).await;
59 }
60 }
61
62 pub async fn ele_displayed(&self, selector: &str, timeout: Option<Duration>) -> Result<bool> {
64 let deadline = Instant::now() + self.timeout_or_default(timeout);
65 loop {
66 if let Some(el) = self.tab.find_once(selector).await?
67 && el.is_displayed().await.unwrap_or(false)
68 {
69 return Ok(true);
70 }
71 if Instant::now() >= deadline {
72 return Ok(false);
73 }
74 tokio::time::sleep(Duration::from_millis(100)).await;
75 }
76 }
77
78 pub async fn ele_deleted(&self, selector: &str, timeout: Option<Duration>) -> Result<bool> {
80 let deadline = Instant::now() + self.timeout_or_default(timeout);
81 loop {
82 if self.tab.find_once(selector).await?.is_none() {
83 return Ok(true);
84 }
85 if Instant::now() >= deadline {
86 return Ok(false);
87 }
88 tokio::time::sleep(Duration::from_millis(100)).await;
89 }
90 }
91
92 pub async fn upload_paths_inputted(&self, timeout: Option<Duration>) -> Result<bool> {
97 self.tab.wait_upload_paths_inputted(timeout).await
98 }
99
100 pub async fn title_contains(&self, sub: &str, timeout: Option<Duration>) -> Result<bool> {
102 let deadline = Instant::now() + self.timeout_or_default(timeout);
103 loop {
104 if self.tab.title().await.unwrap_or_default().contains(sub) {
105 return Ok(true);
106 }
107 if Instant::now() >= deadline {
108 return Ok(false);
109 }
110 tokio::time::sleep(Duration::from_millis(100)).await;
111 }
112 }
113
114 pub async fn url_contains(&self, sub: &str, timeout: Option<Duration>) -> Result<bool> {
116 let deadline = Instant::now() + self.timeout_or_default(timeout);
117 loop {
118 if self.tab.url().await.unwrap_or_default().contains(sub) {
119 return Ok(true);
120 }
121 if Instant::now() >= deadline {
122 return Ok(false);
123 }
124 tokio::time::sleep(Duration::from_millis(100)).await;
125 }
126 }
127
128 pub async fn new_tab(&self, timeout: Option<Duration>) -> Result<Option<Tab>> {
136 let conn = self.tab.core.conn.clone();
137 let opener = self.tab.core.target_id.clone();
138 let ctx = self.tab.core.browser_context_id.clone();
139 let dl = self.tab.core.download_path.clone();
140 let dur = self.timeout_or_default(timeout);
141 let deadline = Instant::now() + dur;
142 let mut events = conn.subscribe();
143 loop {
144 let remain = deadline.saturating_duration_since(Instant::now());
145 if remain.is_zero() {
146 return Ok(None);
147 }
148 let ev = match tokio::time::timeout(remain, events.recv()).await {
149 Ok(Ok(ev)) => ev,
150 Ok(Err(tokio::sync::broadcast::error::RecvError::Lagged(_))) => continue,
151 Ok(Err(tokio::sync::broadcast::error::RecvError::Closed)) => return Ok(None),
152 Err(_) => return Ok(None),
153 };
154 if ev.method != "Browser.attachedToTarget" {
155 continue;
156 }
157 let ti = &ev.params["targetInfo"];
158 if ti["type"].as_str() != Some("page") {
159 continue;
160 }
161 let target_id = ti["targetId"].as_str().unwrap_or_default().to_string();
162 if target_id.is_empty() || target_id == opener {
163 continue;
164 }
165 if ti["browserContextId"].as_str() != Some(ctx.as_str()) {
167 continue;
168 }
169 let Some(session_id) = ev.params["sessionId"].as_str().map(str::to_string) else {
170 continue;
171 };
172 let tab = Tab::from_attached(conn, target_id, ctx, session_id, events, dl, dur).await?;
173 return Ok(Some(tab));
174 }
175 }
176}
177
178pub struct Scroll {
180 tab: Tab,
181}
182
183impl Scroll {
184 pub(crate) fn new(tab: Tab) -> Self {
185 Self { tab }
186 }
187
188 pub async fn to_top(&self) -> Result<()> {
190 self.run("window.scrollTo(window.scrollX, 0)").await
191 }
192
193 pub async fn to_bottom(&self) -> Result<()> {
195 self.run(
196 "window.scrollTo(window.scrollX, \
197 Math.max(document.documentElement.scrollHeight, \
198 document.body ? document.body.scrollHeight : 0))",
199 )
200 .await
201 }
202
203 pub async fn to_left(&self) -> Result<()> {
205 self.run("window.scrollTo(0, window.scrollY)").await
206 }
207
208 pub async fn to_right(&self) -> Result<()> {
210 self.run(
211 "window.scrollTo(Math.max(document.documentElement.scrollWidth, \
212 document.body ? document.body.scrollWidth : 0), window.scrollY)",
213 )
214 .await
215 }
216
217 pub async fn to_half(&self) -> Result<()> {
219 self.run(
220 "window.scrollTo(window.scrollX, \
221 Math.max(document.documentElement.scrollHeight, \
222 document.body ? document.body.scrollHeight : 0) / 2)",
223 )
224 .await
225 }
226
227 pub async fn to_location(&self, x: f64, y: f64) -> Result<()> {
229 self.run(&format!("window.scrollTo({x}, {y})")).await
230 }
231
232 pub async fn up(&self, pixel: f64) -> Result<()> {
234 self.tab.scroll_by(0.0, -pixel).await
235 }
236
237 pub async fn down(&self, pixel: f64) -> Result<()> {
239 self.tab.scroll_by(0.0, pixel).await
240 }
241
242 pub async fn left(&self, pixel: f64) -> Result<()> {
244 self.tab.scroll_by(-pixel, 0.0).await
245 }
246
247 pub async fn right(&self, pixel: f64) -> Result<()> {
249 self.tab.scroll_by(pixel, 0.0).await
250 }
251
252 async fn run(&self, js: &str) -> Result<()> {
253 self.tab.run_js(js).await?;
254 Ok(())
255 }
256}
257
258pub struct SetTab {
260 tab: Tab,
261}
262
263impl SetTab {
264 pub(crate) fn new(tab: Tab) -> Self {
265 Self { tab }
266 }
267
268 pub async fn cookies(&self, cookies: Vec<CookieParam>) -> Result<()> {
270 self.tab.set_cookies(cookies).await
271 }
272
273 pub fn timeout(&self, d: Duration) {
275 self.tab.core.set_timeout(d);
276 }
277
278 pub fn load_mode(&self, m: LoadMode) {
280 self.tab.core.set_load_mode(m);
281 }
282
283 pub async fn user_agent(&self, ua: &str) -> Result<()> {
285 self.tab
286 .core
287 .conn
288 .send(
289 "Browser.setUserAgentOverride",
290 json!({
291 "browserContextId": self.tab.core.browser_context_id,
292 "userAgent": ua,
293 }),
294 None,
295 )
296 .await?;
297 Ok(())
298 }
299
300 pub async fn upload_files(&self, paths: &[&str]) -> Result<()> {
305 self.tab.set_upload_files(paths).await
306 }
307
308 pub async fn local_storage_set(&self, key: &str, value: &str) -> Result<()> {
310 self.storage_set("localStorage", key, value).await
311 }
312 pub async fn local_storage_get(&self, key: &str) -> Result<Option<String>> {
314 self.storage_get("localStorage", key).await
315 }
316 pub async fn local_storage_remove(&self, key: &str) -> Result<()> {
318 self.storage_remove("localStorage", key).await
319 }
320 pub async fn local_storage_clear(&self) -> Result<()> {
322 self.tab.run_js("localStorage.clear()").await?;
323 Ok(())
324 }
325 pub async fn session_storage_set(&self, key: &str, value: &str) -> Result<()> {
327 self.storage_set("sessionStorage", key, value).await
328 }
329 pub async fn session_storage_get(&self, key: &str) -> Result<Option<String>> {
331 self.storage_get("sessionStorage", key).await
332 }
333 pub async fn session_storage_remove(&self, key: &str) -> Result<()> {
335 self.storage_remove("sessionStorage", key).await
336 }
337 pub async fn session_storage_clear(&self) -> Result<()> {
339 self.tab.run_js("sessionStorage.clear()").await?;
340 Ok(())
341 }
342
343 async fn storage_set(&self, store: &str, key: &str, value: &str) -> Result<()> {
344 let js = format!("{store}.setItem({}, {})", js_str(key), js_str(value));
345 self.tab.run_js(&js).await?;
346 Ok(())
347 }
348 async fn storage_get(&self, store: &str, key: &str) -> Result<Option<String>> {
349 let js = format!("{store}.getItem({})", js_str(key));
350 Ok(self.tab.run_js(&js).await?.as_str().map(str::to_string))
351 }
352 async fn storage_remove(&self, store: &str, key: &str) -> Result<()> {
353 let js = format!("{store}.removeItem({})", js_str(key));
354 self.tab.run_js(&js).await?;
355 Ok(())
356 }
357
358 pub fn window(&self) -> Window {
364 Window::new(self.tab.clone())
365 }
366}
367
368fn js_str(s: &str) -> String {
370 serde_json::to_string(s).unwrap_or_else(|_| "\"\"".into())
371}
372
373pub struct Window {
382 tab: Tab,
383}
384
385impl Window {
386 pub(crate) fn new(tab: Tab) -> Self {
387 Self { tab }
388 }
389
390 pub async fn size(&self, width: u32, height: u32) -> Result<()> {
392 self.tab.core.set_viewport_size(width, height).await
393 }
394
395 pub async fn viewport(&self, width: u32, height: u32) -> Result<()> {
397 self.tab.core.set_viewport_size(width, height).await
398 }
399
400 pub async fn max(&self) -> Result<()> {
404 let v = self
405 .tab
406 .run_js("[screen.availWidth, screen.availHeight]")
407 .await?;
408 let w = v.get(0).and_then(|x| x.as_f64()).unwrap_or(1280.0) as u32;
409 let h = v.get(1).and_then(|x| x.as_f64()).unwrap_or(800.0) as u32;
410 self.tab.core.set_viewport_size(w, h).await
411 }
412}
413
414pub struct Intercept {
420 tab: Tab,
421}
422
423impl Intercept {
424 pub(crate) fn new(tab: Tab) -> Self {
425 Self { tab }
426 }
427
428 pub async fn start(&self, targets: &[&str]) -> Result<()> {
430 self.tab.intercept_start(targets).await
431 }
432
433 pub async fn start_xhr(&self, targets: &[&str]) -> Result<()> {
435 self.tab.intercept_xhr(targets).await
436 }
437
438 pub async fn is_intercepting(&self) -> bool {
440 self.tab.core.interceptor.lock().await.is_some()
441 }
442
443 pub async fn next(&self) -> Result<InterceptedRequest> {
445 self.tab.intercept_next().await
446 }
447
448 pub async fn next_timeout(&self, timeout: Duration) -> Result<Option<InterceptedRequest>> {
450 self.tab.intercept_next_timeout(timeout).await
451 }
452
453 pub async fn stop(&self) -> Result<()> {
455 self.tab.intercept_stop().await
456 }
457}
458
459pub struct Listen {
473 tab: Tab,
474}
475
476impl Listen {
477 pub(crate) fn new(tab: Tab) -> Self {
478 Self { tab }
479 }
480
481 pub async fn start(&self, targets: &[&str]) -> Result<()> {
483 self.tab.listen_start(targets).await
484 }
485
486 pub async fn is_listening(&self) -> bool {
488 *self.tab.core.listen_active.lock().await
489 }
490
491 pub async fn peek(&self) -> Vec<DataPacket> {
493 self.tab
494 .core
495 .listen_buf
496 .lock()
497 .await
498 .iter()
499 .cloned()
500 .collect()
501 }
502
503 pub async fn wait(&self) -> Result<DataPacket> {
505 self.tab.listen_wait().await
506 }
507
508 pub async fn wait_timeout(&self, timeout: Duration) -> Result<Option<DataPacket>> {
510 match self.tab.listen_wait_timeout(timeout).await {
511 Ok(p) => Ok(Some(p)),
512 Err(Error::Timeout(_)) => Ok(None),
513 Err(e) => Err(e),
514 }
515 }
516
517 pub async fn next(&self) -> Result<Option<DataPacket>> {
519 self.tab.listen_next().await
520 }
521
522 pub async fn wait_count(
527 &self,
528 count: usize,
529 timeout: Option<Duration>,
530 ) -> Result<Vec<DataPacket>> {
531 let total = timeout.unwrap_or_else(|| self.tab.core.timeout());
532 let deadline = Instant::now() + total;
533 let mut out = Vec::with_capacity(count);
534 while out.len() < count {
535 let remain = deadline.saturating_duration_since(Instant::now());
536 if remain.is_zero() {
537 break;
538 }
539 match self.tab.listen_wait_timeout(remain).await {
540 Ok(p) => out.push(p),
541 Err(Error::Timeout(_)) => break,
542 Err(e) => return Err(e),
543 }
544 }
545 Ok(out)
546 }
547
548 pub async fn steps(&self) -> Result<ListenStream> {
550 self.tab.listen_stream().await
551 }
552
553 pub async fn stop(&self) -> Result<()> {
555 self.tab.listen_stop().await
556 }
557}