1use std::fmt;
26use std::sync::Arc;
27use std::time::Duration;
28
29use base64::Engine;
30use base64::engine::general_purpose::STANDARD as Base64Standard;
31use parking_lot::Mutex as ParkingMutex;
32use serde_json::Value;
33use tokio::sync::oneshot;
34use tokio::time::timeout;
35use tracing::debug;
36
37use crate::error::{Error, Result};
38use crate::identifiers::{ElementId, FrameId, InterceptId, SessionId, SubscriptionId, TabId};
39use crate::protocol::event::ParsedEvent;
40use crate::protocol::{
41 BrowsingContextCommand, Command, Cookie, ElementCommand, Event, EventReply, NetworkCommand,
42 ProxyCommand, Request, Response, ScriptCommand, StorageCommand,
43};
44
45use super::network::{
46 BodyAction, HeadersAction, InterceptedRequest, InterceptedRequestBody,
47 InterceptedRequestHeaders, InterceptedResponse, InterceptedResponseBody, RequestAction,
48 RequestBody,
49};
50use super::proxy::ProxyConfig;
51use super::{Element, Window};
52
53const DEFAULT_WAIT_TIMEOUT: Duration = Duration::from_secs(30);
59
60#[derive(Debug, Clone)]
66pub struct FrameInfo {
67 pub frame_id: FrameId,
69 pub parent_frame_id: Option<FrameId>,
71 pub url: String,
73}
74
75pub(crate) struct TabInner {
77 pub tab_id: TabId,
79 pub frame_id: FrameId,
81 pub session_id: SessionId,
83 pub window: Option<Window>,
85}
86
87#[derive(Clone)]
95pub struct Tab {
96 pub(crate) inner: Arc<TabInner>,
97}
98
99impl fmt::Debug for Tab {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 f.debug_struct("Tab")
102 .field("tab_id", &self.inner.tab_id)
103 .field("frame_id", &self.inner.frame_id)
104 .field("session_id", &self.inner.session_id)
105 .finish_non_exhaustive()
106 }
107}
108
109impl Tab {
110 pub(crate) fn new(
112 tab_id: TabId,
113 frame_id: FrameId,
114 session_id: SessionId,
115 window: Option<Window>,
116 ) -> Self {
117 Self {
118 inner: Arc::new(TabInner {
119 tab_id,
120 frame_id,
121 session_id,
122 window,
123 }),
124 }
125 }
126}
127
128impl Tab {
133 #[inline]
135 #[must_use]
136 pub fn tab_id(&self) -> TabId {
137 self.inner.tab_id
138 }
139
140 #[inline]
142 #[must_use]
143 pub fn frame_id(&self) -> FrameId {
144 self.inner.frame_id
145 }
146
147 #[inline]
149 #[must_use]
150 pub fn session_id(&self) -> SessionId {
151 self.inner.session_id
152 }
153}
154
155impl Tab {
160 pub async fn goto(&self, url: &str) -> Result<()> {
170 debug!(url = %url, tab_id = %self.inner.tab_id, "Navigating");
171
172 let command = Command::BrowsingContext(BrowsingContextCommand::Navigate {
173 url: url.to_string(),
174 });
175
176 self.send_command(command).await?;
177 Ok(())
178 }
179
180 pub async fn navigate(&self, url: &str) -> Result<()> {
182 self.goto(url).await
183 }
184
185 pub async fn load_html(&self, html: &str) -> Result<()> {
199 debug!(tab_id = %self.inner.tab_id, html_len = html.len(), "Loading HTML content");
200
201 let escaped_html = html
202 .replace('\\', "\\\\")
203 .replace('`', "\\`")
204 .replace("${", "\\${");
205
206 let script = format!(
207 r#"(function() {{
208 const html = `{}`;
209 const parser = new DOMParser();
210 const doc = parser.parseFromString(html, 'text/html');
211 const newTitle = doc.querySelector('title');
212 if (newTitle) {{ document.title = newTitle.textContent; }}
213 const newBody = doc.body;
214 if (newBody) {{
215 document.body.innerHTML = newBody.innerHTML;
216 for (const attr of newBody.attributes) {{
217 document.body.setAttribute(attr.name, attr.value);
218 }}
219 }}
220 const newHead = doc.head;
221 if (newHead) {{
222 for (const child of newHead.children) {{
223 if (child.tagName !== 'TITLE') {{
224 document.head.appendChild(child.cloneNode(true));
225 }}
226 }}
227 }}
228 }})();"#,
229 escaped_html
230 );
231
232 self.execute_script(&script).await?;
233 Ok(())
234 }
235
236 pub async fn reload(&self) -> Result<()> {
238 let command = Command::BrowsingContext(BrowsingContextCommand::Reload);
239 self.send_command(command).await?;
240 Ok(())
241 }
242
243 pub async fn back(&self) -> Result<()> {
245 let command = Command::BrowsingContext(BrowsingContextCommand::GoBack);
246 self.send_command(command).await?;
247 Ok(())
248 }
249
250 pub async fn forward(&self) -> Result<()> {
252 let command = Command::BrowsingContext(BrowsingContextCommand::GoForward);
253 self.send_command(command).await?;
254 Ok(())
255 }
256
257 pub async fn get_title(&self) -> Result<String> {
259 let command = Command::BrowsingContext(BrowsingContextCommand::GetTitle);
260 let response = self.send_command(command).await?;
261
262 let title = response
263 .result
264 .as_ref()
265 .and_then(|v| v.get("title"))
266 .and_then(|v| v.as_str())
267 .unwrap_or("")
268 .to_string();
269
270 Ok(title)
271 }
272
273 pub async fn get_url(&self) -> Result<String> {
275 let command = Command::BrowsingContext(BrowsingContextCommand::GetUrl);
276 let response = self.send_command(command).await?;
277
278 let url = response
279 .result
280 .as_ref()
281 .and_then(|v| v.get("url"))
282 .and_then(|v| v.as_str())
283 .unwrap_or("")
284 .to_string();
285
286 Ok(url)
287 }
288
289 pub async fn focus(&self) -> Result<()> {
291 let command = Command::BrowsingContext(BrowsingContextCommand::FocusTab);
292 self.send_command(command).await?;
293 Ok(())
294 }
295
296 pub async fn focus_window(&self) -> Result<()> {
298 let command = Command::BrowsingContext(BrowsingContextCommand::FocusWindow);
299 self.send_command(command).await?;
300 Ok(())
301 }
302
303 pub async fn close(&self) -> Result<()> {
305 let command = Command::BrowsingContext(BrowsingContextCommand::CloseTab);
306 self.send_command(command).await?;
307 Ok(())
308 }
309}
310
311impl Tab {
316 pub async fn switch_to_frame(&self, iframe: &Element) -> Result<Tab> {
331 debug!(tab_id = %self.inner.tab_id, element_id = %iframe.id(), "Switching to frame");
332
333 let command = Command::BrowsingContext(BrowsingContextCommand::SwitchToFrame {
334 element_id: iframe.id().clone(),
335 });
336 let response = self.send_command(command).await?;
337
338 let frame_id = extract_frame_id(&response)?;
339
340 Ok(Tab::new(
341 self.inner.tab_id,
342 FrameId::new(frame_id),
343 self.inner.session_id,
344 self.inner.window.clone(),
345 ))
346 }
347
348 pub async fn switch_to_frame_by_index(&self, index: usize) -> Result<Tab> {
354 debug!(tab_id = %self.inner.tab_id, index, "Switching to frame by index");
355
356 let command =
357 Command::BrowsingContext(BrowsingContextCommand::SwitchToFrameByIndex { index });
358 let response = self.send_command(command).await?;
359
360 let frame_id = extract_frame_id(&response)?;
361
362 Ok(Tab::new(
363 self.inner.tab_id,
364 FrameId::new(frame_id),
365 self.inner.session_id,
366 self.inner.window.clone(),
367 ))
368 }
369
370 pub async fn switch_to_frame_by_url(&self, url_pattern: &str) -> Result<Tab> {
378 debug!(tab_id = %self.inner.tab_id, url_pattern, "Switching to frame by URL");
379
380 let command = Command::BrowsingContext(BrowsingContextCommand::SwitchToFrameByUrl {
381 url_pattern: url_pattern.to_string(),
382 });
383 let response = self.send_command(command).await?;
384
385 let frame_id = extract_frame_id(&response)?;
386
387 Ok(Tab::new(
388 self.inner.tab_id,
389 FrameId::new(frame_id),
390 self.inner.session_id,
391 self.inner.window.clone(),
392 ))
393 }
394
395 pub async fn switch_to_parent_frame(&self) -> Result<Tab> {
397 debug!(tab_id = %self.inner.tab_id, "Switching to parent frame");
398
399 let command = Command::BrowsingContext(BrowsingContextCommand::SwitchToParentFrame);
400 let response = self.send_command(command).await?;
401
402 let frame_id = extract_frame_id(&response)?;
403
404 Ok(Tab::new(
405 self.inner.tab_id,
406 FrameId::new(frame_id),
407 self.inner.session_id,
408 self.inner.window.clone(),
409 ))
410 }
411
412 #[must_use]
414 pub fn switch_to_main_frame(&self) -> Tab {
415 debug!(tab_id = %self.inner.tab_id, "Switching to main frame");
416
417 Tab::new(
418 self.inner.tab_id,
419 FrameId::main(),
420 self.inner.session_id,
421 self.inner.window.clone(),
422 )
423 }
424
425 pub async fn get_frame_count(&self) -> Result<usize> {
427 let command = Command::BrowsingContext(BrowsingContextCommand::GetFrameCount);
428 let response = self.send_command(command).await?;
429
430 let count = response
431 .result
432 .as_ref()
433 .and_then(|v| v.get("count"))
434 .and_then(|v| v.as_u64())
435 .ok_or_else(|| Error::protocol("No count in response"))?;
436
437 Ok(count as usize)
438 }
439
440 pub async fn get_all_frames(&self) -> Result<Vec<FrameInfo>> {
442 let command = Command::BrowsingContext(BrowsingContextCommand::GetAllFrames);
443 let response = self.send_command(command).await?;
444
445 let frames = response
446 .result
447 .as_ref()
448 .and_then(|v| v.get("frames"))
449 .and_then(|v| v.as_array())
450 .map(|arr| arr.iter().filter_map(parse_frame_info).collect())
451 .unwrap_or_default();
452
453 Ok(frames)
454 }
455
456 #[inline]
458 #[must_use]
459 pub fn is_main_frame(&self) -> bool {
460 self.inner.frame_id.is_main()
461 }
462}
463
464impl Tab {
469 pub async fn set_block_rules(&self, patterns: &[&str]) -> Result<()> {
479 debug!(tab_id = %self.inner.tab_id, pattern_count = patterns.len(), "Setting block rules");
480
481 let command = Command::Network(NetworkCommand::SetBlockRules {
482 patterns: patterns.iter().map(|s| (*s).to_string()).collect(),
483 });
484
485 self.send_command(command).await?;
486 Ok(())
487 }
488
489 pub async fn clear_block_rules(&self) -> Result<()> {
491 let command = Command::Network(NetworkCommand::ClearBlockRules);
492 self.send_command(command).await?;
493 Ok(())
494 }
495
496 pub async fn intercept_request<F>(&self, callback: F) -> Result<InterceptId>
516 where
517 F: Fn(InterceptedRequest) -> RequestAction + Send + Sync + 'static,
518 {
519 debug!(tab_id = %self.inner.tab_id, "Enabling request interception");
520
521 let window = self.get_window()?;
522 let callback = Arc::new(callback);
523
524 window.inner.pool.set_event_handler(
525 window.inner.session_id,
526 Box::new(move |event: Event| {
527 if event.method.as_str() != "network.beforeRequestSent" {
528 return None;
529 }
530
531 let request = parse_intercepted_request(&event);
532 let action = callback(request);
533 let result = request_action_to_json(&action);
534
535 Some(EventReply::new(
536 event.id,
537 "network.beforeRequestSent",
538 result,
539 ))
540 }),
541 );
542
543 let command = Command::Network(NetworkCommand::AddIntercept {
544 intercept_requests: true,
545 intercept_request_headers: false,
546 intercept_request_body: false,
547 intercept_responses: false,
548 intercept_response_body: false,
549 });
550
551 let response = self.send_command(command).await?;
552 extract_intercept_id(&response)
553 }
554
555 pub async fn intercept_request_headers<F>(&self, callback: F) -> Result<InterceptId>
557 where
558 F: Fn(InterceptedRequestHeaders) -> HeadersAction + Send + Sync + 'static,
559 {
560 debug!(tab_id = %self.inner.tab_id, "Enabling request headers interception");
561
562 let window = self.get_window()?;
563 let callback = Arc::new(callback);
564
565 window.inner.pool.set_event_handler(
566 window.inner.session_id,
567 Box::new(move |event: Event| {
568 if event.method.as_str() != "network.requestHeaders" {
569 return None;
570 }
571
572 let headers_data = parse_intercepted_request_headers(&event);
573 let action = callback(headers_data);
574 let result = headers_action_to_json(&action);
575
576 Some(EventReply::new(event.id, "network.requestHeaders", result))
577 }),
578 );
579
580 let command = Command::Network(NetworkCommand::AddIntercept {
581 intercept_requests: false,
582 intercept_request_headers: true,
583 intercept_request_body: false,
584 intercept_responses: false,
585 intercept_response_body: false,
586 });
587
588 let response = self.send_command(command).await?;
589 extract_intercept_id(&response)
590 }
591
592 pub async fn intercept_request_body<F>(&self, callback: F) -> Result<InterceptId>
594 where
595 F: Fn(InterceptedRequestBody) + Send + Sync + 'static,
596 {
597 debug!(tab_id = %self.inner.tab_id, "Enabling request body interception");
598
599 let window = self.get_window()?;
600 let callback = Arc::new(callback);
601
602 window.inner.pool.set_event_handler(
603 window.inner.session_id,
604 Box::new(move |event: Event| {
605 if event.method.as_str() != "network.requestBody" {
606 return None;
607 }
608
609 let body_data = parse_intercepted_request_body(&event);
610 callback(body_data);
611
612 Some(EventReply::new(
613 event.id,
614 "network.requestBody",
615 serde_json::json!({ "action": "allow" }),
616 ))
617 }),
618 );
619
620 let command = Command::Network(NetworkCommand::AddIntercept {
621 intercept_requests: false,
622 intercept_request_headers: false,
623 intercept_request_body: true,
624 intercept_responses: false,
625 intercept_response_body: false,
626 });
627
628 let response = self.send_command(command).await?;
629 extract_intercept_id(&response)
630 }
631
632 pub async fn intercept_response<F>(&self, callback: F) -> Result<InterceptId>
634 where
635 F: Fn(InterceptedResponse) -> HeadersAction + Send + Sync + 'static,
636 {
637 debug!(tab_id = %self.inner.tab_id, "Enabling response interception");
638
639 let window = self.get_window()?;
640 let callback = Arc::new(callback);
641
642 window.inner.pool.set_event_handler(
643 window.inner.session_id,
644 Box::new(move |event: Event| {
645 if event.method.as_str() != "network.responseHeaders" {
646 return None;
647 }
648
649 let resp = parse_intercepted_response(&event);
650 let action = callback(resp);
651 let result = headers_action_to_json(&action);
652
653 Some(EventReply::new(event.id, "network.responseHeaders", result))
654 }),
655 );
656
657 let command = Command::Network(NetworkCommand::AddIntercept {
658 intercept_requests: false,
659 intercept_request_headers: false,
660 intercept_request_body: false,
661 intercept_responses: true,
662 intercept_response_body: false,
663 });
664
665 let response = self.send_command(command).await?;
666 extract_intercept_id(&response)
667 }
668
669 pub async fn intercept_response_body<F>(&self, callback: F) -> Result<InterceptId>
671 where
672 F: Fn(InterceptedResponseBody) -> BodyAction + Send + Sync + 'static,
673 {
674 debug!(tab_id = %self.inner.tab_id, "Enabling response body interception");
675
676 let window = self.get_window()?;
677 let callback = Arc::new(callback);
678
679 window.inner.pool.set_event_handler(
680 window.inner.session_id,
681 Box::new(move |event: Event| {
682 if event.method.as_str() != "network.responseBody" {
683 return None;
684 }
685
686 let body_data = parse_intercepted_response_body(&event);
687 let action = callback(body_data);
688 let result = body_action_to_json(&action);
689
690 Some(EventReply::new(event.id, "network.responseBody", result))
691 }),
692 );
693
694 let command = Command::Network(NetworkCommand::AddIntercept {
695 intercept_requests: false,
696 intercept_request_headers: false,
697 intercept_request_body: false,
698 intercept_responses: false,
699 intercept_response_body: true,
700 });
701
702 let response = self.send_command(command).await?;
703 extract_intercept_id(&response)
704 }
705
706 pub async fn stop_intercept(&self, intercept_id: &InterceptId) -> Result<()> {
712 debug!(tab_id = %self.inner.tab_id, %intercept_id, "Stopping interception");
713
714 let window = self.get_window()?;
715 window
716 .inner
717 .pool
718 .clear_event_handler(window.inner.session_id);
719
720 let command = Command::Network(NetworkCommand::RemoveIntercept {
721 intercept_id: intercept_id.clone(),
722 });
723
724 self.send_command(command).await?;
725 Ok(())
726 }
727}
728
729impl Tab {
734 pub async fn set_proxy(&self, config: ProxyConfig) -> Result<()> {
746 debug!(tab_id = %self.inner.tab_id, proxy_type = %config.proxy_type.as_str(), "Setting proxy");
747
748 let command = Command::Proxy(ProxyCommand::SetTabProxy {
749 proxy_type: config.proxy_type.as_str().to_string(),
750 host: config.host,
751 port: config.port,
752 username: config.username,
753 password: config.password,
754 proxy_dns: config.proxy_dns,
755 });
756
757 self.send_command(command).await?;
758 Ok(())
759 }
760
761 pub async fn clear_proxy(&self) -> Result<()> {
763 let command = Command::Proxy(ProxyCommand::ClearTabProxy);
764 self.send_command(command).await?;
765 Ok(())
766 }
767}
768
769impl Tab {
774 pub async fn get_cookie(&self, name: &str) -> Result<Option<Cookie>> {
776 let command = Command::Storage(StorageCommand::GetCookie {
777 name: name.to_string(),
778 url: None,
779 });
780
781 let response = self.send_command(command).await?;
782
783 let cookie = response
784 .result
785 .as_ref()
786 .and_then(|v| v.get("cookie"))
787 .and_then(|v| serde_json::from_value::<Cookie>(v.clone()).ok());
788
789 Ok(cookie)
790 }
791
792 pub async fn set_cookie(&self, cookie: Cookie) -> Result<()> {
802 let command = Command::Storage(StorageCommand::SetCookie { cookie, url: None });
803 self.send_command(command).await?;
804 Ok(())
805 }
806
807 pub async fn delete_cookie(&self, name: &str) -> Result<()> {
809 let command = Command::Storage(StorageCommand::DeleteCookie {
810 name: name.to_string(),
811 url: None,
812 });
813
814 self.send_command(command).await?;
815 Ok(())
816 }
817
818 pub async fn get_all_cookies(&self) -> Result<Vec<Cookie>> {
820 let command = Command::Storage(StorageCommand::GetAllCookies { url: None });
821 let response = self.send_command(command).await?;
822
823 let cookies = response
824 .result
825 .as_ref()
826 .and_then(|v| v.get("cookies"))
827 .and_then(|v| v.as_array())
828 .map(|arr| {
829 arr.iter()
830 .filter_map(|v| serde_json::from_value::<Cookie>(v.clone()).ok())
831 .collect()
832 })
833 .unwrap_or_default();
834
835 Ok(cookies)
836 }
837}
838
839impl Tab {
844 pub async fn local_storage_get(&self, key: &str) -> Result<Option<String>> {
846 let script = format!("return localStorage.getItem({});", json_string(key));
847 let value = self.execute_script(&script).await?;
848
849 match value {
850 Value::Null => Ok(None),
851 Value::String(s) => Ok(Some(s)),
852 _ => Ok(value.as_str().map(|s| s.to_string())),
853 }
854 }
855
856 pub async fn local_storage_set(&self, key: &str, value: &str) -> Result<()> {
858 let script = format!(
859 "localStorage.setItem({}, {});",
860 json_string(key),
861 json_string(value)
862 );
863
864 self.execute_script(&script).await?;
865 Ok(())
866 }
867
868 pub async fn local_storage_delete(&self, key: &str) -> Result<()> {
870 let script = format!("localStorage.removeItem({});", json_string(key));
871 self.execute_script(&script).await?;
872 Ok(())
873 }
874
875 pub async fn local_storage_clear(&self) -> Result<()> {
877 self.execute_script("localStorage.clear();").await?;
878 Ok(())
879 }
880}
881
882impl Tab {
887 pub async fn session_storage_get(&self, key: &str) -> Result<Option<String>> {
889 let script = format!("return sessionStorage.getItem({});", json_string(key));
890 let value = self.execute_script(&script).await?;
891
892 match value {
893 Value::Null => Ok(None),
894 Value::String(s) => Ok(Some(s)),
895 _ => Ok(value.as_str().map(|s| s.to_string())),
896 }
897 }
898
899 pub async fn session_storage_set(&self, key: &str, value: &str) -> Result<()> {
901 let script = format!(
902 "sessionStorage.setItem({}, {});",
903 json_string(key),
904 json_string(value)
905 );
906
907 self.execute_script(&script).await?;
908 Ok(())
909 }
910
911 pub async fn session_storage_delete(&self, key: &str) -> Result<()> {
913 let script = format!("sessionStorage.removeItem({});", json_string(key));
914 self.execute_script(&script).await?;
915 Ok(())
916 }
917
918 pub async fn session_storage_clear(&self) -> Result<()> {
920 self.execute_script("sessionStorage.clear();").await?;
921 Ok(())
922 }
923}
924
925impl Tab {
930 pub async fn execute_script(&self, script: &str) -> Result<Value> {
940 let command = Command::Script(ScriptCommand::Evaluate {
941 script: script.to_string(),
942 args: vec![],
943 });
944
945 let response = self.send_command(command).await?;
946
947 let value = response
948 .result
949 .as_ref()
950 .and_then(|v| v.get("value"))
951 .cloned()
952 .unwrap_or(Value::Null);
953
954 Ok(value)
955 }
956
957 pub async fn execute_async_script(&self, script: &str) -> Result<Value> {
961 let command = Command::Script(ScriptCommand::EvaluateAsync {
962 script: script.to_string(),
963 args: vec![],
964 });
965
966 let response = self.send_command(command).await?;
967
968 let value = response
969 .result
970 .as_ref()
971 .and_then(|v| v.get("value"))
972 .cloned()
973 .unwrap_or(Value::Null);
974
975 Ok(value)
976 }
977}
978
979impl Tab {
984 pub async fn find_element(&self, selector: &str) -> Result<Element> {
990 let command = Command::Element(ElementCommand::Find {
991 selector: selector.to_string(),
992 parent_id: None,
993 });
994
995 let response = self.send_command(command).await?;
996
997 let element_id = response
998 .result
999 .as_ref()
1000 .and_then(|v| v.get("elementId"))
1001 .and_then(|v| v.as_str())
1002 .ok_or_else(|| {
1003 Error::element_not_found(selector, self.inner.tab_id, self.inner.frame_id)
1004 })?;
1005
1006 Ok(Element::new(
1007 ElementId::new(element_id),
1008 self.inner.tab_id,
1009 self.inner.frame_id,
1010 self.inner.session_id,
1011 self.inner.window.clone(),
1012 ))
1013 }
1014
1015 pub async fn find_elements(&self, selector: &str) -> Result<Vec<Element>> {
1017 let command = Command::Element(ElementCommand::FindAll {
1018 selector: selector.to_string(),
1019 parent_id: None,
1020 });
1021
1022 let response = self.send_command(command).await?;
1023
1024 let elements = response
1025 .result
1026 .as_ref()
1027 .and_then(|v| v.get("elementIds"))
1028 .and_then(|v| v.as_array())
1029 .map(|arr| {
1030 arr.iter()
1031 .filter_map(|v| v.as_str())
1032 .map(|id| {
1033 Element::new(
1034 ElementId::new(id),
1035 self.inner.tab_id,
1036 self.inner.frame_id,
1037 self.inner.session_id,
1038 self.inner.window.clone(),
1039 )
1040 })
1041 .collect()
1042 })
1043 .unwrap_or_default();
1044
1045 Ok(elements)
1046 }
1047}
1048
1049impl Tab {
1054 pub async fn wait_for_element(&self, selector: &str) -> Result<Element> {
1062 self.wait_for_element_timeout(selector, DEFAULT_WAIT_TIMEOUT)
1063 .await
1064 }
1065
1066 pub async fn wait_for_element_timeout(
1073 &self,
1074 selector: &str,
1075 timeout_duration: Duration,
1076 ) -> Result<Element> {
1077 debug!(
1078 tab_id = %self.inner.tab_id,
1079 selector,
1080 timeout_ms = timeout_duration.as_millis(),
1081 "Waiting for element"
1082 );
1083
1084 let window = self.get_window()?;
1085
1086 let (tx, rx) = oneshot::channel::<Result<Element>>();
1087 let tx = Arc::new(ParkingMutex::new(Some(tx)));
1088 let selector_clone = selector.to_string();
1089 let tab_id = self.inner.tab_id;
1090 let frame_id = self.inner.frame_id;
1091 let session_id = self.inner.session_id;
1092 let window_clone = self.inner.window.clone();
1093 let tx_clone = Arc::clone(&tx);
1094
1095 window.inner.pool.set_event_handler(
1096 window.inner.session_id,
1097 Box::new(move |event: Event| {
1098 if event.method.as_str() != "element.added" {
1099 return None;
1100 }
1101
1102 let parsed = event.parse();
1103 if let ParsedEvent::ElementAdded {
1104 selector: event_selector,
1105 element_id,
1106 ..
1107 } = parsed
1108 && event_selector == selector_clone
1109 {
1110 let element = Element::new(
1111 ElementId::new(&element_id),
1112 tab_id,
1113 frame_id,
1114 session_id,
1115 window_clone.clone(),
1116 );
1117
1118 if let Some(tx) = tx_clone.lock().take() {
1119 let _ = tx.send(Ok(element));
1120 }
1121 }
1122
1123 None
1124 }),
1125 );
1126
1127 let command = Command::Element(ElementCommand::Subscribe {
1128 selector: selector.to_string(),
1129 one_shot: true,
1130 });
1131 let response = self.send_command(command).await?;
1132
1133 if let Some(element_id) = response
1135 .result
1136 .as_ref()
1137 .and_then(|v| v.get("elementId"))
1138 .and_then(|v| v.as_str())
1139 {
1140 window
1141 .inner
1142 .pool
1143 .clear_event_handler(window.inner.session_id);
1144
1145 return Ok(Element::new(
1146 ElementId::new(element_id),
1147 self.inner.tab_id,
1148 self.inner.frame_id,
1149 self.inner.session_id,
1150 self.inner.window.clone(),
1151 ));
1152 }
1153
1154 let result = timeout(timeout_duration, rx).await;
1155
1156 window
1157 .inner
1158 .pool
1159 .clear_event_handler(window.inner.session_id);
1160
1161 match result {
1162 Ok(Ok(element)) => element,
1163 Ok(Err(_)) => Err(Error::protocol("Channel closed unexpectedly")),
1164 Err(_) => Err(Error::Timeout {
1165 operation: format!("wait_for_element({})", selector),
1166 timeout_ms: timeout_duration.as_millis() as u64,
1167 }),
1168 }
1169 }
1170
1171 pub async fn on_element_added<F>(&self, selector: &str, callback: F) -> Result<SubscriptionId>
1177 where
1178 F: Fn(Element) + Send + Sync + 'static,
1179 {
1180 debug!(tab_id = %self.inner.tab_id, selector, "Subscribing to element.added");
1181
1182 let window = self.get_window()?;
1183
1184 let selector_clone = selector.to_string();
1185 let tab_id = self.inner.tab_id;
1186 let frame_id = self.inner.frame_id;
1187 let session_id = self.inner.session_id;
1188 let window_clone = self.inner.window.clone();
1189 let callback = Arc::new(callback);
1190
1191 window.inner.pool.set_event_handler(
1192 window.inner.session_id,
1193 Box::new(move |event: Event| {
1194 if event.method.as_str() != "element.added" {
1195 return None;
1196 }
1197
1198 let parsed = event.parse();
1199 if let ParsedEvent::ElementAdded {
1200 selector: event_selector,
1201 element_id,
1202 ..
1203 } = parsed
1204 && event_selector == selector_clone
1205 {
1206 let element = Element::new(
1207 ElementId::new(&element_id),
1208 tab_id,
1209 frame_id,
1210 session_id,
1211 window_clone.clone(),
1212 );
1213 callback(element);
1214 }
1215
1216 None
1217 }),
1218 );
1219
1220 let command = Command::Element(ElementCommand::Subscribe {
1221 selector: selector.to_string(),
1222 one_shot: false,
1223 });
1224
1225 let response = self.send_command(command).await?;
1226
1227 let subscription_id = response
1228 .result
1229 .as_ref()
1230 .and_then(|v| v.get("subscriptionId"))
1231 .and_then(|v| v.as_str())
1232 .ok_or_else(|| Error::protocol("No subscriptionId in response"))?;
1233
1234 Ok(SubscriptionId::new(subscription_id))
1235 }
1236
1237 pub async fn on_element_removed<F>(&self, element_id: &ElementId, callback: F) -> Result<()>
1239 where
1240 F: Fn() + Send + Sync + 'static,
1241 {
1242 debug!(tab_id = %self.inner.tab_id, %element_id, "Watching for element removal");
1243
1244 let window = self.get_window()?;
1245
1246 let element_id_clone = element_id.as_str().to_string();
1247 let callback = Arc::new(callback);
1248
1249 window.inner.pool.set_event_handler(
1250 window.inner.session_id,
1251 Box::new(move |event: Event| {
1252 if event.method.as_str() != "element.removed" {
1253 return None;
1254 }
1255
1256 let parsed = event.parse();
1257 if let ParsedEvent::ElementRemoved {
1258 element_id: removed_id,
1259 ..
1260 } = parsed
1261 && removed_id == element_id_clone
1262 {
1263 callback();
1264 }
1265
1266 None
1267 }),
1268 );
1269
1270 let command = Command::Element(ElementCommand::WatchRemoval {
1271 element_id: element_id.clone(),
1272 });
1273
1274 self.send_command(command).await?;
1275 Ok(())
1276 }
1277
1278 pub async fn unsubscribe(&self, subscription_id: &SubscriptionId) -> Result<()> {
1280 let command = Command::Element(ElementCommand::Unsubscribe {
1281 subscription_id: subscription_id.as_str().to_string(),
1282 });
1283
1284 self.send_command(command).await?;
1285
1286 if let Some(window) = &self.inner.window {
1287 window
1288 .inner
1289 .pool
1290 .clear_event_handler(window.inner.session_id);
1291 }
1292
1293 Ok(())
1294 }
1295}
1296
1297impl Tab {
1302 pub(crate) async fn send_command(&self, command: Command) -> Result<Response> {
1304 let window = self.get_window()?;
1305 let request = Request::new(self.inner.tab_id, self.inner.frame_id, command);
1306 window
1307 .inner
1308 .pool
1309 .send(window.inner.session_id, request)
1310 .await
1311 }
1312
1313 fn get_window(&self) -> Result<&Window> {
1315 self.inner
1316 .window
1317 .as_ref()
1318 .ok_or_else(|| Error::protocol("Tab has no associated window"))
1319 }
1320}
1321
1322fn json_string(s: &str) -> String {
1328 serde_json::to_string(s).unwrap_or_else(|_| format!("\"{}\"", s))
1329}
1330
1331fn extract_frame_id(response: &Response) -> Result<u64> {
1333 response
1334 .result
1335 .as_ref()
1336 .and_then(|v| v.get("frameId"))
1337 .and_then(|v| v.as_u64())
1338 .ok_or_else(|| Error::protocol("No frameId in response"))
1339}
1340
1341fn extract_intercept_id(response: &Response) -> Result<InterceptId> {
1343 let id = response
1344 .result
1345 .as_ref()
1346 .and_then(|v| v.get("interceptId"))
1347 .and_then(|v| v.as_str())
1348 .ok_or_else(|| Error::protocol("No interceptId in response"))?;
1349
1350 Ok(InterceptId::new(id))
1351}
1352
1353fn parse_frame_info(v: &Value) -> Option<FrameInfo> {
1355 Some(FrameInfo {
1356 frame_id: FrameId::new(v.get("frameId")?.as_u64()?),
1357 parent_frame_id: v
1358 .get("parentFrameId")
1359 .and_then(|p| p.as_i64())
1360 .and_then(|p| {
1361 if p < 0 {
1362 None
1363 } else {
1364 Some(FrameId::new(p as u64))
1365 }
1366 }),
1367 url: v.get("url")?.as_str()?.to_string(),
1368 })
1369}
1370
1371fn parse_intercepted_request(event: &Event) -> InterceptedRequest {
1373 InterceptedRequest {
1374 request_id: event
1375 .params
1376 .get("requestId")
1377 .and_then(|v| v.as_str())
1378 .unwrap_or("")
1379 .to_string(),
1380 url: event
1381 .params
1382 .get("url")
1383 .and_then(|v| v.as_str())
1384 .unwrap_or("")
1385 .to_string(),
1386 method: event
1387 .params
1388 .get("method")
1389 .and_then(|v| v.as_str())
1390 .unwrap_or("GET")
1391 .to_string(),
1392 resource_type: event
1393 .params
1394 .get("resourceType")
1395 .and_then(|v| v.as_str())
1396 .unwrap_or("other")
1397 .to_string(),
1398 tab_id: event
1399 .params
1400 .get("tabId")
1401 .and_then(|v| v.as_u64())
1402 .unwrap_or(0) as u32,
1403 frame_id: event
1404 .params
1405 .get("frameId")
1406 .and_then(|v| v.as_u64())
1407 .unwrap_or(0),
1408 body: None,
1409 }
1410}
1411
1412fn parse_intercepted_request_headers(event: &Event) -> InterceptedRequestHeaders {
1414 InterceptedRequestHeaders {
1415 request_id: event
1416 .params
1417 .get("requestId")
1418 .and_then(|v| v.as_str())
1419 .unwrap_or("")
1420 .to_string(),
1421 url: event
1422 .params
1423 .get("url")
1424 .and_then(|v| v.as_str())
1425 .unwrap_or("")
1426 .to_string(),
1427 method: event
1428 .params
1429 .get("method")
1430 .and_then(|v| v.as_str())
1431 .unwrap_or("GET")
1432 .to_string(),
1433 headers: event
1434 .params
1435 .get("headers")
1436 .and_then(|v| v.as_object())
1437 .map(|obj| {
1438 obj.iter()
1439 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
1440 .collect()
1441 })
1442 .unwrap_or_default(),
1443 tab_id: event
1444 .params
1445 .get("tabId")
1446 .and_then(|v| v.as_u64())
1447 .unwrap_or(0) as u32,
1448 frame_id: event
1449 .params
1450 .get("frameId")
1451 .and_then(|v| v.as_u64())
1452 .unwrap_or(0),
1453 }
1454}
1455
1456fn parse_intercepted_request_body(event: &Event) -> InterceptedRequestBody {
1458 InterceptedRequestBody {
1459 request_id: event
1460 .params
1461 .get("requestId")
1462 .and_then(|v| v.as_str())
1463 .unwrap_or("")
1464 .to_string(),
1465 url: event
1466 .params
1467 .get("url")
1468 .and_then(|v| v.as_str())
1469 .unwrap_or("")
1470 .to_string(),
1471 method: event
1472 .params
1473 .get("method")
1474 .and_then(|v| v.as_str())
1475 .unwrap_or("GET")
1476 .to_string(),
1477 resource_type: event
1478 .params
1479 .get("resourceType")
1480 .and_then(|v| v.as_str())
1481 .unwrap_or("other")
1482 .to_string(),
1483 tab_id: event
1484 .params
1485 .get("tabId")
1486 .and_then(|v| v.as_u64())
1487 .unwrap_or(0) as u32,
1488 frame_id: event
1489 .params
1490 .get("frameId")
1491 .and_then(|v| v.as_u64())
1492 .unwrap_or(0),
1493 body: event.params.as_object().and_then(parse_request_body),
1494 }
1495}
1496
1497fn parse_intercepted_response(event: &Event) -> InterceptedResponse {
1499 InterceptedResponse {
1500 request_id: event
1501 .params
1502 .get("requestId")
1503 .and_then(|v| v.as_str())
1504 .unwrap_or("")
1505 .to_string(),
1506 url: event
1507 .params
1508 .get("url")
1509 .and_then(|v| v.as_str())
1510 .unwrap_or("")
1511 .to_string(),
1512 status: event
1513 .params
1514 .get("status")
1515 .and_then(|v| v.as_u64())
1516 .unwrap_or(0) as u16,
1517 status_text: event
1518 .params
1519 .get("statusText")
1520 .and_then(|v| v.as_str())
1521 .unwrap_or("")
1522 .to_string(),
1523 headers: event
1524 .params
1525 .get("headers")
1526 .and_then(|v| v.as_object())
1527 .map(|obj| {
1528 obj.iter()
1529 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
1530 .collect()
1531 })
1532 .unwrap_or_default(),
1533 tab_id: event
1534 .params
1535 .get("tabId")
1536 .and_then(|v| v.as_u64())
1537 .unwrap_or(0) as u32,
1538 frame_id: event
1539 .params
1540 .get("frameId")
1541 .and_then(|v| v.as_u64())
1542 .unwrap_or(0),
1543 }
1544}
1545
1546fn parse_intercepted_response_body(event: &Event) -> InterceptedResponseBody {
1548 InterceptedResponseBody {
1549 request_id: event
1550 .params
1551 .get("requestId")
1552 .and_then(|v| v.as_str())
1553 .unwrap_or("")
1554 .to_string(),
1555 url: event
1556 .params
1557 .get("url")
1558 .and_then(|v| v.as_str())
1559 .unwrap_or("")
1560 .to_string(),
1561 tab_id: event
1562 .params
1563 .get("tabId")
1564 .and_then(|v| v.as_u64())
1565 .unwrap_or(0) as u32,
1566 frame_id: event
1567 .params
1568 .get("frameId")
1569 .and_then(|v| v.as_u64())
1570 .unwrap_or(0),
1571 body: event
1572 .params
1573 .get("body")
1574 .and_then(|v| v.as_str())
1575 .unwrap_or("")
1576 .to_string(),
1577 content_length: event
1578 .params
1579 .get("contentLength")
1580 .and_then(|v| v.as_u64())
1581 .unwrap_or(0) as usize,
1582 }
1583}
1584
1585fn parse_request_body(params: &serde_json::Map<String, Value>) -> Option<RequestBody> {
1587 let body = params.get("body")?;
1588 let body_obj = body.as_object()?;
1589
1590 if let Some(error) = body_obj.get("error").and_then(|v| v.as_str()) {
1591 return Some(RequestBody::Error(error.to_string()));
1592 }
1593
1594 if let Some(form_data) = body_obj.get("data").and_then(|v| v.as_object())
1595 && body_obj.get("type").and_then(|v| v.as_str()) == Some("formData")
1596 {
1597 let mut map = std::collections::HashMap::new();
1598 for (key, value) in form_data {
1599 if let Some(arr) = value.as_array() {
1600 let values: Vec<String> = arr
1601 .iter()
1602 .filter_map(|v| v.as_str().map(|s| s.to_string()))
1603 .collect();
1604 map.insert(key.clone(), values);
1605 }
1606 }
1607 return Some(RequestBody::FormData(map));
1608 }
1609
1610 if let Some(raw_data) = body_obj.get("data").and_then(|v| v.as_array())
1611 && body_obj.get("type").and_then(|v| v.as_str()) == Some("raw")
1612 {
1613 let mut bytes = Vec::new();
1614 for item in raw_data {
1615 if let Some(obj) = item.as_object()
1616 && let Some(b64) = obj.get("data").and_then(|v| v.as_str())
1617 && let Ok(decoded) = Base64Standard.decode(b64)
1618 {
1619 bytes.extend(decoded);
1620 }
1621 }
1622 if !bytes.is_empty() {
1623 return Some(RequestBody::Raw(bytes));
1624 }
1625 }
1626
1627 None
1628}
1629
1630fn request_action_to_json(action: &RequestAction) -> Value {
1632 match action {
1633 RequestAction::Allow => serde_json::json!({ "action": "allow" }),
1634 RequestAction::Block => serde_json::json!({ "action": "block" }),
1635 RequestAction::Redirect(url) => serde_json::json!({ "action": "redirect", "url": url }),
1636 }
1637}
1638
1639fn headers_action_to_json(action: &HeadersAction) -> Value {
1641 match action {
1642 HeadersAction::Allow => serde_json::json!({ "action": "allow" }),
1643 HeadersAction::ModifyHeaders(h) => {
1644 serde_json::json!({ "action": "modifyHeaders", "headers": h })
1645 }
1646 }
1647}
1648
1649fn body_action_to_json(action: &BodyAction) -> Value {
1651 match action {
1652 BodyAction::Allow => serde_json::json!({ "action": "allow" }),
1653 BodyAction::ModifyBody(b) => serde_json::json!({ "action": "modifyBody", "body": b }),
1654 }
1655}
1656
1657#[cfg(test)]
1662mod tests {
1663 use super::Tab;
1664
1665 #[test]
1666 fn test_tab_is_clone() {
1667 fn assert_clone<T: Clone>() {}
1668 assert_clone::<Tab>();
1669 }
1670
1671 #[test]
1672 fn test_tab_is_debug() {
1673 fn assert_debug<T: std::fmt::Debug>() {}
1674 assert_debug::<Tab>();
1675 }
1676}