1use crate::DomainType;
2use crate::accessibility::AccessibilityController;
3use crate::browser::{
4 discovery::{find_running_browser_port, find_running_browser_port_by_process_name},
5 launcher::BrowserType,
6 ws_endpoints::resolve_active_page_ws_url,
7};
8use crate::domain_manager::DomainManager;
9use crate::emulation::EmulationController;
10use crate::error::{CdpError, Result};
11use crate::input::{keyboard::Keyboard, mouse::Mouse};
12use crate::network::network_intercept::{
13 NetworkEventCallback, NetworkMonitor, ResponseMonitorManager,
14};
15use crate::page::{element::ElementHandle, frame::Frame};
16use crate::session::Session;
17use crate::tracing::{TracingController, TracingSessionState};
18use crate::transport::{cdp_protocol::*, websocket_connection::*};
19use cdp_protocol::page::Viewport;
20use cdp_protocol::{
21 page::{
22 self as page_cdp, FrameTree, GetFrameTree, GetFrameTreeReturnObject, Navigate,
23 NavigateReturnObject,
24 },
25 runtime::{Evaluate, EvaluateReturnObject},
26};
27use futures_util::Stream;
28use futures_util::StreamExt;
29use std::collections::HashMap;
30use std::path::PathBuf;
31use std::pin::Pin;
32use std::sync::Arc;
33use tokio::fs::File;
34use tokio::io::AsyncWriteExt;
35use tokio::sync::Mutex;
36use tokio::sync::broadcast;
37use tokio::sync::mpsc;
38use tokio_stream::wrappers::BroadcastStream;
39use tokio_tungstenite::connect_async;
40
41#[derive(Clone, Debug)]
43pub enum FrameLifecycleEvent {
44 Attached {
46 frame_id: String,
47 parent_frame_id: Option<String>,
48 },
49 Detached { frame_id: String },
51 Navigated { frame_id: String, url: String },
53}
54
55pub type FrameLifecycleCallback = Arc<dyn Fn(FrameLifecycleEvent) + Send + Sync>;
57
58#[derive(Clone, Debug)]
60pub enum DomMutationEvent {
61 ChildNodeInserted {
63 parent_node_id: u32,
64 previous_node_id: u32,
65 node: serde_json::Value,
66 },
67 ChildNodeRemoved { parent_node_id: u32, node_id: u32 },
69 AttributeModified {
71 node_id: u32,
72 name: String,
73 value: String,
74 },
75 AttributeRemoved { node_id: u32, name: String },
77 CharacterDataModified {
79 node_id: u32,
80 character_data: String,
81 },
82}
83
84#[derive(Debug, Clone, Default)]
86pub struct WaitForNavigationOptions {
87 pub timeout_ms: Option<u64>,
89 pub wait_until: Option<WaitUntil>,
91}
92
93#[derive(Debug, Clone, Default)]
95pub struct WaitForSelectorOptions {
96 pub timeout_ms: Option<u64>,
98 pub visible: Option<bool>,
100 pub hidden: Option<bool>,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
106pub enum WaitUntil {
107 Load,
110
111 DOMContentLoaded,
114
115 NetworkIdle0,
118
119 #[default]
122 NetworkIdle2,
123}
124
125pub type DomMutationCallback = Arc<dyn Fn(DomMutationEvent) + Send + Sync>;
127
128#[derive(Clone, Debug)]
130pub struct ExecutionContextInfo {
131 pub id: u32,
133 pub unique_id: String,
135}
136
137pub struct Page {
139 pub(crate) session: Arc<Session>,
140 pub contexts: Arc<Mutex<HashMap<String, ExecutionContextInfo>>>,
142 pub domain_manager: Arc<DomainManager>,
144 pub network_monitor: Arc<NetworkMonitor>,
146 pub response_monitor_manager: Arc<ResponseMonitorManager>,
148 main_frame_cache: Arc<Mutex<Option<Frame>>>,
150 frames_cache: Arc<Mutex<HashMap<String, Frame>>>,
152 frame_parent_map: Arc<Mutex<HashMap<String, String>>>,
154 frame_children_map: Arc<Mutex<HashMap<String, Vec<String>>>>,
156 lifecycle_callbacks: Arc<Mutex<Vec<FrameLifecycleCallback>>>,
158 dom_mutation_callbacks: Arc<Mutex<Vec<DomMutationCallback>>>,
160 tracing_state: Arc<Mutex<TracingSessionState>>,
162 pub(crate) file_chooser_lock: Arc<Mutex<()>>,
164}
165
166impl Page {
167 pub(crate) fn new_from_browser(session: Arc<Session>) -> Self {
169 let domain_manager = Arc::new(DomainManager::new(Arc::clone(&session)));
170 Self {
171 session,
172 contexts: Arc::new(Mutex::new(HashMap::new())),
173 domain_manager,
174 main_frame_cache: Arc::new(Mutex::new(None)),
175 frames_cache: Arc::new(Mutex::new(HashMap::new())),
176 frame_parent_map: Arc::new(Mutex::new(HashMap::new())),
177 frame_children_map: Arc::new(Mutex::new(HashMap::new())),
178 lifecycle_callbacks: Arc::new(Mutex::new(Vec::new())),
179 dom_mutation_callbacks: Arc::new(Mutex::new(Vec::new())),
180 network_monitor: Arc::new(NetworkMonitor::default()),
181 response_monitor_manager: Arc::new(ResponseMonitorManager::default()),
182 tracing_state: Arc::new(Mutex::new(TracingSessionState::default())),
183 file_chooser_lock: Arc::new(Mutex::new(())),
184 }
185 }
186
187 pub async fn connect_to_active_page(
189 port: Option<u16>,
190 pattern: Option<&str>,
191 ) -> Result<Arc<Self>> {
192 let port = Self::resolve_remote_debug_port(port, None)?;
193 Self::connect_to_active_page_inner(port, pattern).await
194 }
195
196 pub async fn connect_to_active_page_with_browser(
198 port: Option<u16>,
199 pattern: Option<&str>,
200 browser_process: Option<&str>,
201 ) -> Result<Arc<Self>> {
202 let port = Self::resolve_remote_debug_port(port, browser_process)?;
203 Self::connect_to_active_page_inner(port, pattern).await
204 }
205
206 fn resolve_remote_debug_port(port: Option<u16>, browser_process: Option<&str>) -> Result<u16> {
207 if let Some(explicit) = port {
208 return Ok(explicit);
209 }
210
211 if let Some(process_name) = browser_process {
212 return find_running_browser_port_by_process_name(process_name);
213 }
214
215 for bt in [
216 BrowserType::Chrome,
217 BrowserType::Edge,
218 BrowserType::Chromium,
219 ] {
220 if let Ok(found) = find_running_browser_port(bt) {
221 return Ok(found);
222 }
223 }
224
225 Err(CdpError::tool(
226 "Found no running browser with remote debugging enabled, please specify browser type or port explicitly",
227 ))
228 }
229
230 async fn connect_to_active_page_inner(port: u16, pattern: Option<&str>) -> Result<Arc<Self>> {
231 let page_ws_url = resolve_active_page_ws_url(port, pattern).await?;
232 let (ws_stream, _) = connect_async(page_ws_url.as_str()).await?;
233 let (writer, reader) = ws_stream.split();
234 let (event_sender, event_receiver) = mpsc::channel(crate::DEFAULT_CHANNEL_CAPACITY);
236 let connection = Arc::new(ConnectionInternals::new(writer));
237
238 let session = Session::new(None, connection.clone(), event_receiver);
239 let session = Arc::new(session);
240 let domain_manager = Arc::new(DomainManager::new(Arc::clone(&session)));
241
242 let page = Arc::new(Page {
243 session,
244 contexts: Arc::new(Mutex::new(HashMap::new())),
245 domain_manager,
246 main_frame_cache: Arc::new(Mutex::new(None)),
247 frames_cache: Arc::new(Mutex::new(HashMap::new())),
248 frame_parent_map: Arc::new(Mutex::new(HashMap::new())),
249 frame_children_map: Arc::new(Mutex::new(HashMap::new())),
250 lifecycle_callbacks: Arc::new(Mutex::new(Vec::new())),
251 dom_mutation_callbacks: Arc::new(Mutex::new(Vec::new())),
252 network_monitor: Arc::new(NetworkMonitor::default()),
253 response_monitor_manager: Arc::new(ResponseMonitorManager::default()),
254 tracing_state: Arc::new(Mutex::new(TracingSessionState::default())),
255 file_chooser_lock: Arc::new(Mutex::new(())),
256 });
257
258 let page_weak = Arc::downgrade(&page);
260
261 tokio::spawn(direct_message_dispatcher(
263 reader,
264 connection.clone(),
265 event_sender,
266 page_weak,
267 ));
268
269 page.domain_manager.enable_required_domains().await?;
271
272 Ok(page)
273 }
274
275 pub fn mouse(&self) -> Mouse {
277 Mouse::new(Arc::clone(&self.session), Arc::clone(&self.domain_manager))
278 }
279
280 pub fn keyboard(&self) -> Keyboard {
282 Keyboard::new(Arc::clone(&self.session), Arc::clone(&self.domain_manager))
283 }
284
285 pub fn accessibility(self: &Arc<Self>) -> AccessibilityController {
286 AccessibilityController::new(Arc::clone(self))
287 }
288
289 pub fn emulation(self: &Arc<Self>) -> EmulationController {
290 EmulationController::new(Arc::clone(self))
291 }
292
293 pub fn tracing(self: &Arc<Self>) -> TracingController {
294 TracingController::new(Arc::clone(self), Arc::clone(&self.tracing_state))
295 }
296
297 pub async fn navigate(&self, url: &str) -> Result<NavigateReturnObject> {
298 let navigate = Navigate {
299 url: url.to_string(),
300 referrer: None,
301 transition_type: None,
302 frame_id: None,
303 referrer_policy: None,
304 };
305 self.session
306 .send_command::<_, NavigateReturnObject>(navigate, None)
307 .await
308 }
309
310 fn events(&self) -> broadcast::Receiver<CdpEvent> {
311 self.session.event_bus.subscribe()
312 }
313
314 pub fn on<E>(&self) -> Pin<Box<dyn Stream<Item = E> + Send + 'static>>
315 where
316 E: TryFrom<CdpEvent> + Send + 'static,
317 <E as TryFrom<CdpEvent>>::Error: Send,
318 {
319 let stream = BroadcastStream::new(self.events())
320 .filter_map(|result| async { result.ok() })
321 .filter_map(|event| async { E::try_from(event).ok() });
322 Box::pin(stream)
323 }
324
325 pub(crate) async fn wait_for<E>(&self) -> Result<E>
326 where
327 E: TryFrom<CdpEvent> + Send + 'static,
328 <E as TryFrom<CdpEvent>>::Error: Send,
329 {
330 self.on::<E>().next().await.ok_or_else(|| {
331 CdpError::page("Event stream closed before event was received.".to_string())
332 })
333 }
334
335 pub async fn wait_for_loaded(&self) -> Result<page_cdp::events::LoadEventFiredEvent> {
336 self.wait_for::<page_cdp::events::LoadEventFiredEvent>()
337 .await
338 }
339
340 pub async fn get_title(&self) -> Result<String> {
341 let method = Evaluate {
342 expression: "document.title".to_string(),
343 object_group: None,
344 include_command_line_api: None,
345 silent: None,
346 context_id: None,
347 return_by_value: None,
348 generate_preview: None,
349 user_gesture: None,
350 await_promise: None,
351 throw_on_side_effect: None,
352 timeout: None,
353 disable_breaks: None,
354 repl_mode: None,
355 allow_unsafe_eval_blocked_by_csp: None,
356 unique_context_id: None,
357 serialization_options: None,
358 };
359 let eval_result = self
360 .session
361 .send_command::<_, EvaluateReturnObject>(method, None)
362 .await?;
363
364 let title = eval_result
366 .result
367 .value
368 .and_then(|v| v.as_str().map(ToString::to_string))
369 .ok_or_else(|| {
370 CdpError::page("Could not extract title string from evaluation result".to_string())
371 })?;
372
373 Ok(title)
374 }
375
376 pub async fn main_frame(self: &Arc<Self>) -> Result<Frame> {
378 {
379 let cache = self.main_frame_cache.lock().await;
380 if let Some(frame) = cache.as_ref() {
381 return Ok(frame.clone());
382 }
383 }
384 let method = GetFrameTree(None);
385 let obj = self
386 .session
387 .send_command::<_, GetFrameTreeReturnObject>(method, None)
388 .await?;
389 let main_frame_id = obj.frame_tree.frame.id.clone();
390 let main_frame = Frame::new(main_frame_id.clone(), Arc::clone(self));
391 {
392 let mut cache = self.main_frame_cache.lock().await;
393 *cache = Some(main_frame.clone());
394 }
395 {
396 let mut frames = self.frames_cache.lock().await;
397 frames.insert(main_frame_id, main_frame.clone());
398 }
399
400 Ok(main_frame)
401 }
402
403 pub async fn query_selector(self: &Arc<Self>, selector: &str) -> Result<Option<ElementHandle>> {
428 let main_frame = self.main_frame().await?;
430 match main_frame.query_selector(selector).await {
431 Ok(Some(element)) => return Ok(Some(element)),
432 Ok(None) => {} Err(e) => {
434 eprintln!("Warning: Failed to query in main frame: {}", e);
436 }
437 }
438
439 let frames = self.all_frames().await?;
441 for frame in frames.iter().skip(1) {
442 match frame.query_selector(selector).await {
444 Ok(Some(element)) => return Ok(Some(element)),
445 Ok(None) => continue, Err(e) => {
447 eprintln!("Warning: Failed to query in frame {}: {}", frame.id(), e);
449 continue;
450 }
451 }
452 }
453
454 Ok(None)
455 }
456
457 pub async fn query_selector_all(
486 self: &Arc<Self>,
487 selector: &str,
488 ) -> Result<Vec<ElementHandle>> {
489 let frames = self.all_frames().await?;
491 let mut all_elements = Vec::new();
492
493 for frame in frames {
495 match frame.query_selector_all(selector).await {
496 Ok(elements) => {
497 all_elements.extend(elements);
498 }
499 Err(e) => {
500 eprintln!("Warning: Failed to query in frame {}: {}", frame.id(), e);
502 continue;
503 }
504 }
505 }
506
507 Ok(all_elements)
508 }
509
510 pub async fn all_frames(self: &Arc<Self>) -> Result<Vec<Frame>> {
512 let method = GetFrameTree(None);
513 let result = self
514 .session
515 .send_command::<_, GetFrameTreeReturnObject>(method, None)
516 .await?;
517 let mut all_frames = Vec::new();
518 let mut new_cache = HashMap::new();
519 let mut new_parent_map = HashMap::new();
520 let mut new_children_map = HashMap::new();
521
522 Self::collect_frames_recursive(
523 &result.frame_tree,
524 None, Arc::clone(self),
526 &mut all_frames,
527 &mut new_cache,
528 &mut new_parent_map,
529 &mut new_children_map,
530 );
531
532 {
534 let mut frames_cache = self.frames_cache.lock().await;
535 *frames_cache = new_cache;
536 }
537
538 {
539 let mut parent_map = self.frame_parent_map.lock().await;
540 *parent_map = new_parent_map;
541 }
542
543 {
544 let mut children_map = self.frame_children_map.lock().await;
545 *children_map = new_children_map;
546 }
547
548 {
549 let mut main_cache = self.main_frame_cache.lock().await;
550 if main_cache.is_none() && !all_frames.is_empty() {
551 *main_cache = Some(all_frames[0].clone());
552 }
553 }
554
555 Ok(all_frames)
556 }
557
558 fn collect_frames_recursive(
560 frame_tree: &FrameTree,
561 parent_id: Option<String>,
562 page_arc: Arc<Page>,
563 frames_list: &mut Vec<Frame>,
564 frames_map: &mut HashMap<String, Frame>,
565 parent_map: &mut HashMap<String, String>,
566 children_map: &mut HashMap<String, Vec<String>>,
567 ) {
568 let frame_id = frame_tree.frame.id.clone();
569
570 let current_frame = Frame::new(frame_id.clone(), Arc::clone(&page_arc));
572
573 frames_list.push(current_frame.clone());
575 frames_map.insert(frame_id.clone(), current_frame);
576
577 if let Some(ref parent) = parent_id {
579 parent_map.insert(frame_id.clone(), parent.clone());
580 children_map
581 .entry(parent.clone())
582 .or_default()
583 .push(frame_id.clone());
584 }
585
586 if let Some(child_frames) = &frame_tree.child_frames {
588 for child_tree in child_frames {
589 Self::collect_frames_recursive(
590 child_tree,
591 Some(frame_id.clone()),
592 Arc::clone(&page_arc),
593 frames_list,
594 frames_map,
595 parent_map,
596 children_map,
597 );
598 }
599 }
600 }
601
602 pub async fn get_frame(&self, frame_id: &str) -> Option<Frame> {
604 self.frames_cache.lock().await.get(frame_id).cloned()
605 }
606
607 pub async fn clear_frame_cache(&self) {
609 self.main_frame_cache.lock().await.take();
610 self.frames_cache.lock().await.clear();
611 }
612
613 pub async fn register_execution_context(
615 &self,
616 frame_id: String,
617 context_id: u32,
618 unique_id: String,
619 ) {
620 self.contexts.lock().await.insert(
621 frame_id,
622 ExecutionContextInfo {
623 id: context_id,
624 unique_id,
625 },
626 );
627 }
628
629 pub async fn remove_execution_context(&self, unique_id: String) {
631 let mut contexts = self.contexts.lock().await;
632 contexts.retain(|_frame_id, info| info.unique_id != unique_id);
634 }
635
636 pub async fn clear_execution_contexts(&self) {
638 self.contexts.lock().await.clear();
639 }
640
641 pub async fn get_parent_frame(&self, frame_id: &str) -> Option<Frame> {
645 let parent_map = self.frame_parent_map.lock().await;
646 let parent_id = parent_map.get(frame_id)?;
647 self.get_frame(parent_id).await
648 }
649
650 pub async fn get_child_frames(&self, frame_id: &str) -> Vec<Frame> {
652 let children_map = self.frame_children_map.lock().await;
653 let child_ids = children_map.get(frame_id);
654
655 match child_ids {
656 Some(ids) => {
657 let mut children = Vec::new();
658 for id in ids {
659 if let Some(frame) = self.get_frame(id).await {
660 children.push(frame);
661 }
662 }
663 children
664 }
665 None => Vec::new(),
666 }
667 }
668
669 pub async fn get_ancestor_frames(&self, frame_id: &str) -> Vec<Frame> {
671 let mut ancestors = Vec::new();
672 let mut current_id = frame_id.to_string();
673
674 loop {
675 let parent = self.get_parent_frame(¤t_id).await;
676 match parent {
677 Some(frame) => {
678 current_id = frame.id.clone();
679 ancestors.push(frame);
680 }
681 None => break,
682 }
683 }
684
685 ancestors
686 }
687
688 pub async fn get_descendant_frames(&self, frame_id: &str) -> Vec<Frame> {
690 let mut descendants = Vec::new();
691 self.collect_descendants_recursive(frame_id, &mut descendants)
692 .await;
693 descendants
694 }
695
696 fn collect_descendants_recursive<'a>(
698 &'a self,
699 frame_id: &'a str,
700 result: &'a mut Vec<Frame>,
701 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 'a>> {
702 Box::pin(async move {
703 let children = self.get_child_frames(frame_id).await;
704 for child in children {
705 result.push(child.clone());
706 self.collect_descendants_recursive(&child.id, result).await;
707 }
708 })
709 }
710
711 pub async fn query_frame(self: &Arc<Self>, selector: &str) -> Result<Option<Frame>> {
739 let frames = self.all_frames().await?;
740
741 for frame in frames {
742 if self.matches_selector(&frame, selector).await? {
743 return Ok(Some(frame));
744 }
745 }
746
747 Ok(None)
748 }
749
750 pub async fn query_frames(self: &Arc<Self>, selector: &str) -> Result<Vec<Frame>> {
752 let frames = self.all_frames().await?;
753 let mut matched = Vec::new();
754
755 for frame in frames {
756 if self.matches_selector(&frame, selector).await? {
757 matched.push(frame);
758 }
759 }
760
761 Ok(matched)
762 }
763
764 async fn matches_selector(&self, frame: &Frame, selector: &str) -> Result<bool> {
766 if let Some(name_pattern) = selector.strip_prefix("name:") {
767 let frame_name = frame.name().await?;
768 return Ok(frame_name.as_deref() == Some(name_pattern));
769 }
770
771 if let Some(url_prefix) = selector.strip_prefix("url:") {
772 let frame_url = frame.url().await?;
773 return Ok(frame_url.starts_with(url_prefix));
774 }
775
776 if let Some(url_pattern) = selector.strip_prefix("url~") {
777 let frame_url = frame.url().await?;
778 return Ok(frame_url.contains(url_pattern));
780 }
781
782 if let Some(depth_str) = selector.strip_prefix("depth:")
783 && let Ok(target_depth) = depth_str.parse::<usize>()
784 {
785 let ancestors = self.get_ancestor_frames(&frame.id).await;
786 return Ok(ancestors.len() == target_depth);
787 }
788
789 Ok(false)
790 }
791
792 pub async fn on_frame_lifecycle(&self, callback: FrameLifecycleCallback) {
818 self.lifecycle_callbacks.lock().await.push(callback);
819 }
820
821 pub(crate) async fn trigger_lifecycle_event(&self, event: FrameLifecycleEvent) {
823 let callbacks = self.lifecycle_callbacks.lock().await;
824 for callback in callbacks.iter() {
825 callback(event.clone());
826 }
827 }
828
829 pub(crate) async fn handle_frame_attached(
831 &self,
832 frame_id: String,
833 parent_frame_id: Option<String>,
834 ) {
835 if let Some(ref parent_id) = parent_frame_id {
837 self.frame_parent_map
838 .lock()
839 .await
840 .insert(frame_id.clone(), parent_id.clone());
841
842 self.frame_children_map
843 .lock()
844 .await
845 .entry(parent_id.clone())
846 .or_insert_with(Vec::new)
847 .push(frame_id.clone());
848 }
849
850 self.trigger_lifecycle_event(FrameLifecycleEvent::Attached {
852 frame_id,
853 parent_frame_id,
854 })
855 .await;
856 }
857
858 pub(crate) async fn handle_frame_detached(&self, frame_id: String) {
860 self.frames_cache.lock().await.remove(&frame_id);
862
863 let parent_id = self.frame_parent_map.lock().await.remove(&frame_id);
865 if let Some(parent) = parent_id {
866 let mut children_map = self.frame_children_map.lock().await;
867 if let Some(children) = children_map.get_mut(&parent) {
868 children.retain(|id| id != &frame_id);
869 }
870 }
871 self.frame_children_map.lock().await.remove(&frame_id);
872
873 self.trigger_lifecycle_event(FrameLifecycleEvent::Detached { frame_id })
875 .await;
876 }
877
878 pub(crate) async fn handle_frame_navigated(&self, frame_id: String, url: String) {
880 self.frames_cache.lock().await.remove(&frame_id);
882
883 self.trigger_lifecycle_event(FrameLifecycleEvent::Navigated { frame_id, url })
885 .await;
886 }
887
888 pub async fn on_dom_mutation(&self, callback: DomMutationCallback) {
917 self.dom_mutation_callbacks.lock().await.push(callback);
918 }
919
920 pub async fn enable_dom_mutations(&self) -> Result<()> {
922 use cdp_protocol::dom;
923
924 let enable = dom::Enable {
926 include_whitespace: None,
927 };
928 self.session
929 .send_command::<_, dom::EnableReturnObject>(enable, None)
930 .await?;
931
932 println!("DOM mutation monitoring enabled");
933 Ok(())
934 }
935
936 pub(crate) async fn trigger_dom_mutation_event(&self, event: DomMutationEvent) {
938 let callbacks = self.dom_mutation_callbacks.lock().await;
939 for callback in callbacks.iter() {
940 callback(event.clone());
941 }
942 }
943
944 pub(crate) async fn handle_child_node_inserted(
946 &self,
947 parent_node_id: u32,
948 previous_node_id: u32,
949 node: serde_json::Value,
950 ) {
951 self.trigger_dom_mutation_event(DomMutationEvent::ChildNodeInserted {
952 parent_node_id,
953 previous_node_id,
954 node,
955 })
956 .await;
957 }
958
959 pub(crate) async fn handle_child_node_removed(&self, parent_node_id: u32, node_id: u32) {
961 self.trigger_dom_mutation_event(DomMutationEvent::ChildNodeRemoved {
962 parent_node_id,
963 node_id,
964 })
965 .await;
966 }
967
968 pub(crate) async fn handle_attribute_modified(
970 &self,
971 node_id: u32,
972 name: String,
973 value: String,
974 ) {
975 self.trigger_dom_mutation_event(DomMutationEvent::AttributeModified {
976 node_id,
977 name,
978 value,
979 })
980 .await;
981 }
982
983 pub(crate) async fn handle_attribute_removed(&self, node_id: u32, name: String) {
985 self.trigger_dom_mutation_event(DomMutationEvent::AttributeRemoved { node_id, name })
986 .await;
987 }
988
989 pub(crate) async fn handle_character_data_modified(
991 &self,
992 node_id: u32,
993 character_data: String,
994 ) {
995 self.trigger_dom_mutation_event(DomMutationEvent::CharacterDataModified {
996 node_id,
997 character_data,
998 })
999 .await;
1000 }
1001}
1002
1003#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
1007pub struct FrameSnapshot {
1008 pub frame_id: String,
1010 pub url: String,
1012 pub name: Option<String>,
1014 pub parent_frame_id: Option<String>,
1016 pub child_frame_ids: Vec<String>,
1018 pub timestamp: u64,
1020 pub html_content: Option<String>,
1022 pub dom_depth: Option<usize>,
1024}
1025
1026impl Page {
1027 pub async fn create_frame_snapshot(
1051 &self,
1052 frame_id: &str,
1053 include_html: bool,
1054 ) -> Result<FrameSnapshot> {
1055 let frame = self
1056 .get_frame(frame_id)
1057 .await
1058 .ok_or_else(|| CdpError::page(format!("Frame not found: {}", frame_id)))?;
1059
1060 let url = frame.url().await.unwrap_or_else(|_| "".to_string());
1061 let name = frame.name().await.unwrap_or(None);
1062 let parent_frame_id = self.frame_parent_map.lock().await.get(frame_id).cloned();
1063 let child_frame_ids = self
1064 .frame_children_map
1065 .lock()
1066 .await
1067 .get(frame_id)
1068 .cloned()
1069 .unwrap_or_default();
1070
1071 let html_content = if include_html {
1072 match frame.evaluate("document.documentElement.outerHTML").await {
1073 Ok(html_value) => html_value.as_str().map(|s| s.to_string()),
1074 Err(_) => None,
1075 }
1076 } else {
1077 None
1078 };
1079
1080 let dom_depth = if include_html {
1081 match frame
1082 .evaluate(
1083 r#"
1084 (function() {
1085 function getDepth(element) {
1086 if (!element || !element.children) return 0;
1087 let maxDepth = 0;
1088 for (let child of element.children) {
1089 maxDepth = Math.max(maxDepth, getDepth(child));
1090 }
1091 return maxDepth + 1;
1092 }
1093 return getDepth(document.documentElement);
1094 })()
1095 "#,
1096 )
1097 .await
1098 {
1099 Ok(depth_value) => depth_value.as_u64().map(|d| d as usize),
1100 Err(_) => None,
1101 }
1102 } else {
1103 None
1104 };
1105
1106 let timestamp = std::time::SystemTime::now()
1107 .duration_since(std::time::UNIX_EPOCH)
1108 .unwrap_or_default()
1109 .as_secs();
1110
1111 Ok(FrameSnapshot {
1112 frame_id: frame_id.to_string(),
1113 url,
1114 name,
1115 parent_frame_id,
1116 child_frame_ids,
1117 timestamp,
1118 html_content,
1119 dom_depth,
1120 })
1121 }
1122
1123 pub async fn create_all_frames_snapshot(
1140 self: &Arc<Self>,
1141 include_html: bool,
1142 ) -> Result<Vec<FrameSnapshot>> {
1143 let frames = self.all_frames().await?;
1144 let mut snapshots = Vec::new();
1145
1146 for frame in frames {
1147 match self.create_frame_snapshot(&frame.id, include_html).await {
1148 Ok(snapshot) => snapshots.push(snapshot),
1149 Err(e) => {
1150 eprintln!("Failed to create snapshot for frame {}: {}", frame.id, e);
1151 }
1152 }
1153 }
1154
1155 Ok(snapshots)
1156 }
1157
1158 pub fn compare_snapshots(snapshot1: &FrameSnapshot, snapshot2: &FrameSnapshot) -> Vec<String> {
1180 let mut differences = Vec::new();
1181
1182 if snapshot1.frame_id != snapshot2.frame_id {
1183 differences.push(format!(
1184 "Frame ID changed: {} -> {}",
1185 snapshot1.frame_id, snapshot2.frame_id
1186 ));
1187 }
1188
1189 if snapshot1.url != snapshot2.url {
1190 differences.push(format!(
1191 "URL changed: {} -> {}",
1192 snapshot1.url, snapshot2.url
1193 ));
1194 }
1195
1196 if snapshot1.name != snapshot2.name {
1197 differences.push(format!(
1198 "Name changed: {:?} -> {:?}",
1199 snapshot1.name, snapshot2.name
1200 ));
1201 }
1202
1203 if snapshot1.parent_frame_id != snapshot2.parent_frame_id {
1204 differences.push(format!(
1205 "Parent Frame ID changed: {:?} -> {:?}",
1206 snapshot1.parent_frame_id, snapshot2.parent_frame_id
1207 ));
1208 }
1209
1210 if snapshot1.child_frame_ids != snapshot2.child_frame_ids {
1211 differences.push(format!(
1212 "Child Frames changed: {} -> {}",
1213 snapshot1.child_frame_ids.len(),
1214 snapshot2.child_frame_ids.len()
1215 ));
1216 }
1217
1218 if let (Some(html1), Some(html2)) = (&snapshot1.html_content, &snapshot2.html_content)
1219 && html1 != html2
1220 {
1221 differences.push(format!(
1222 "HTML content changed: {} bytes -> {} bytes",
1223 html1.len(),
1224 html2.len()
1225 ));
1226 }
1227
1228 if snapshot1.dom_depth != snapshot2.dom_depth {
1229 differences.push(format!(
1230 "DOM depth changed: {:?} -> {:?}",
1231 snapshot1.dom_depth, snapshot2.dom_depth
1232 ));
1233 }
1234
1235 differences
1236 }
1237
1238 pub async fn type_text(&self, text: &str) -> Result<()> {
1262 self.keyboard().insert_text(text).await
1263 }
1264
1265 pub async fn type_text_with_delay(
1291 &self,
1292 text: &str,
1293 min_delay_ms: u64,
1294 max_delay_ms: u64,
1295 ) -> Result<()> {
1296 self.keyboard()
1297 .type_text_with_delay(text, min_delay_ms, max_delay_ms)
1298 .await
1299 }
1300
1301 async fn get_device_pixel_ratio(&self) -> Result<f64> {
1306 let eval_result = self
1307 .session
1308 .send_command::<_, EvaluateReturnObject>(
1309 Evaluate {
1310 expression: "window.devicePixelRatio".to_string(),
1311 object_group: None,
1312 include_command_line_api: None,
1313 silent: None,
1314 context_id: None,
1315 return_by_value: Some(true),
1316 generate_preview: None,
1317 user_gesture: None,
1318 await_promise: None,
1319 throw_on_side_effect: None,
1320 timeout: None,
1321 disable_breaks: None,
1322 repl_mode: None,
1323 allow_unsafe_eval_blocked_by_csp: None,
1324 unique_context_id: None,
1325 serialization_options: None,
1326 },
1327 None,
1328 )
1329 .await?;
1330
1331 let dpr = eval_result
1332 .result
1333 .value
1334 .and_then(|v| v.as_f64())
1335 .unwrap_or(1.0);
1336
1337 Ok(dpr)
1338 }
1339
1340 pub async fn screenshot(&self, full_page: bool, save_path: Option<PathBuf>) -> Result<String> {
1367 self.screenshot_with_options(full_page, save_path, true)
1368 .await
1369 }
1370
1371 pub async fn screenshot_with_options(
1397 &self,
1398 full_page: bool,
1399 save_path: Option<PathBuf>,
1400 auto_resolve_dpr: bool,
1401 ) -> Result<String> {
1402 use base64::Engine;
1403 use cdp_protocol::page as page_cdp;
1404
1405 let device_scale = if auto_resolve_dpr {
1407 let dpr = self.get_device_pixel_ratio().await?;
1408 dpr.clamp(0.5, 3.0)
1410 } else {
1411 1.0
1412 };
1413
1414 let mut clip: Option<Viewport> = None;
1415 let layout_metrics_params = page_cdp::GetLayoutMetrics(None);
1416 let layout_metrics: page_cdp::GetLayoutMetricsReturnObject = self
1417 .session
1418 .send_command(layout_metrics_params, None)
1419 .await?;
1420 let visual_viewport_scale = layout_metrics.css_visual_viewport.scale;
1421 let device_scale_factor = device_scale.max(visual_viewport_scale);
1422 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1423
1424 if let Some(mut c) = clip.as_ref().cloned() {
1425 let dpr = c.scale.max(1.0);
1426 let to_device = |v: f64| (v * dpr).round();
1427 let x_dev = to_device(c.x);
1428 let y_dev = to_device(c.y);
1429 let x_px = (x_dev.floor() - 1.0).max(0.0);
1430 let y_px = (y_dev.floor() - 1.0).max(0.0);
1431 let right_px = to_device(c.x + c.width).ceil();
1432 let bottom_px = to_device(c.y + c.height).ceil();
1433 let w_px = (right_px - x_px).max(1.0);
1434 let h_px = (bottom_px - y_px).max(1.0);
1435 c.x = x_px / dpr;
1436 c.y = y_px / dpr;
1437 c.width = w_px / dpr;
1438 c.height = h_px / dpr;
1439 c.scale = device_scale_factor;
1440 clip = Some(c);
1441 }
1442
1443 let screenshot_params = page_cdp::CaptureScreenshot {
1445 format: Some(page_cdp::CaptureScreenshotFormatOption::Png),
1446 quality: None,
1447 clip,
1448 from_surface: Some(true),
1449 capture_beyond_viewport: Some(full_page),
1450 optimize_for_speed: None,
1451 };
1452
1453 let result: page_cdp::CaptureScreenshotReturnObject =
1454 self.session.send_command(screenshot_params, None).await?;
1455
1456 let out_path_buf: std::path::PathBuf = match save_path {
1458 Some(pv) => {
1459 if pv.parent().is_none_or(|p| p.as_os_str().is_empty()) {
1460 std::env::current_dir()?.join(pv)
1461 } else {
1462 if let Some(parent) = pv.parent() {
1463 std::fs::create_dir_all(parent)?;
1464 }
1465 pv.to_path_buf()
1466 }
1467 }
1468 None => {
1469 let out_dir = std::env::var("OUT_PATH").unwrap_or_else(|_| ".".to_string());
1470 let dir = std::path::PathBuf::from(out_dir);
1471 std::fs::create_dir_all(&dir)?;
1472 let nanos = std::time::SystemTime::now()
1473 .duration_since(std::time::UNIX_EPOCH)
1474 .unwrap_or_default()
1475 .as_nanos();
1476 dir.join(format!("screenshot-{}.png", nanos))
1477 }
1478 };
1479
1480 let bytes = base64::engine::general_purpose::STANDARD
1482 .decode(&result.data)
1483 .map_err(|err| {
1484 CdpError::page(format!("Failed to decode screenshot response: {}", err))
1485 })?;
1486 let mut f = File::create(&out_path_buf).await?;
1487 f.write_all(&bytes).await?;
1488 f.flush().await?;
1489
1490 let out_path = out_path_buf.to_string_lossy();
1491 Ok(out_path.into_owned())
1492 }
1493
1494 pub async fn wait_for_selector(
1526 self: &Arc<Self>,
1527 selector: &str,
1528 options: Option<WaitForSelectorOptions>,
1529 ) -> Result<ElementHandle> {
1530 let opts = options.unwrap_or_default();
1531 let timeout_ms = opts.timeout_ms.unwrap_or(30000);
1532 let visible = opts.visible.unwrap_or(false);
1533 let hidden = opts.hidden.unwrap_or(false);
1534
1535 let start = std::time::Instant::now();
1536 let poll_interval = std::time::Duration::from_millis(100);
1537
1538 loop {
1539 if start.elapsed().as_millis() > timeout_ms as u128 {
1541 return Err(CdpError::page(format!(
1542 "Timeout waiting for selector '{}' ({}ms)",
1543 selector, timeout_ms
1544 )));
1545 }
1546
1547 if let Some(element) = self.query_selector(selector).await? {
1549 if visible
1551 && let Ok(is_visible) = element.is_visible().await
1552 && !is_visible
1553 {
1554 tokio::time::sleep(poll_interval).await;
1555 continue;
1556 }
1557
1558 if hidden
1560 && let Ok(is_visible) = element.is_visible().await
1561 && is_visible
1562 {
1563 tokio::time::sleep(poll_interval).await;
1564 continue;
1565 }
1566
1567 return Ok(element);
1568 }
1569
1570 tokio::time::sleep(poll_interval).await;
1571 }
1572 }
1573
1574 pub async fn wait_for_selector_hidden(
1591 self: &Arc<Self>,
1592 selector: &str,
1593 timeout_ms: Option<u64>,
1594 ) -> Result<()> {
1595 let timeout = timeout_ms.unwrap_or(30000);
1596 let start = std::time::Instant::now();
1597 let poll_interval = std::time::Duration::from_millis(100);
1598
1599 loop {
1600 if start.elapsed().as_millis() > timeout as u128 {
1601 return Err(CdpError::page(format!(
1602 "Timeout waiting for selector '{}' to be hidden ({}ms)",
1603 selector, timeout
1604 )));
1605 }
1606
1607 match self.query_selector(selector).await? {
1608 None => return Ok(()), Some(element) => {
1610 if let Ok(is_visible) = element.is_visible().await
1612 && !is_visible
1613 {
1614 return Ok(());
1615 }
1616 }
1617 }
1618
1619 tokio::time::sleep(poll_interval).await;
1620 }
1621 }
1622
1623 pub async fn wait_for_function(
1652 self: &Arc<Self>,
1653 function: &str,
1654 timeout_ms: Option<u64>,
1655 poll_interval_ms: Option<u64>,
1656 ) -> Result<()> {
1657 let timeout = timeout_ms.unwrap_or(30000);
1658 let poll_interval = std::time::Duration::from_millis(poll_interval_ms.unwrap_or(100));
1659 let start = std::time::Instant::now();
1660
1661 let script = format!(
1663 r#"
1664 (() => {{
1665 try {{
1666 const candidate = ({});
1667 const value = typeof candidate === 'function' ? candidate() : candidate;
1668 if (value && typeof value.then === 'function') {{
1669 return value.then(Boolean).catch(() => false);
1670 }}
1671 return Boolean(value);
1672 }} catch (error) {{
1673 return false;
1674 }}
1675 }})()
1676 "#,
1677 function
1678 );
1679
1680 loop {
1681 if start.elapsed().as_millis() > timeout as u128 {
1682 return Err(CdpError::page(format!(
1683 "Timeout waiting for function ({}ms)",
1684 timeout
1685 )));
1686 }
1687
1688 let main_frame = match self.main_frame().await {
1690 Ok(frame) => frame,
1691 Err(_) => {
1692 tokio::time::sleep(poll_interval).await;
1693 continue;
1694 }
1695 };
1696
1697 match main_frame.evaluate(&script).await {
1698 Ok(result) => {
1699 if let Some(true) = result.as_bool() {
1700 return Ok(());
1701 }
1702 }
1703 Err(_) => {
1704 }
1706 }
1707
1708 tokio::time::sleep(poll_interval).await;
1709 }
1710 }
1711
1712 pub async fn wait_for_navigation(
1746 self: &Arc<Self>,
1747 options: Option<WaitForNavigationOptions>,
1748 ) -> Result<()> {
1749 let opts = options.unwrap_or_default();
1750 let timeout = opts.timeout_ms.unwrap_or(60000);
1751 let wait_until = opts.wait_until.unwrap_or(WaitUntil::NetworkIdle2);
1752
1753 match wait_until {
1754 WaitUntil::Load => {
1755 self.wait_for_function(
1757 "() => document.readyState === 'complete'",
1758 Some(timeout),
1759 None,
1760 )
1761 .await
1762 }
1763 WaitUntil::DOMContentLoaded => {
1764 self.wait_for_function(
1766 "() => document.readyState === 'interactive'",
1767 Some(timeout),
1768 None,
1769 )
1770 .await
1771 }
1772 WaitUntil::NetworkIdle0 => {
1773 self.wait_for_network_idle(timeout, 0).await
1775 }
1776 WaitUntil::NetworkIdle2 => {
1777 self.wait_for_network_idle(timeout, 2).await
1779 }
1780 }
1781 }
1782
1783 pub async fn on_network(&self, callback: NetworkEventCallback) {
1810 self.network_monitor.add_callback(callback).await;
1811 }
1812
1813 pub fn get_inflight_requests_count(&self) -> usize {
1828 self.network_monitor.get_inflight_count()
1829 }
1830
1831 async fn wait_for_network_idle(
1840 self: &Arc<Self>,
1841 timeout_ms: u64,
1842 max_inflight: usize,
1843 ) -> Result<()> {
1844 if !self.domain_manager.is_enabled(DomainType::Network).await {
1846 self.domain_manager.enable_network_domain().await?;
1847 }
1848
1849 self.network_monitor.reset_inflight().await;
1851
1852 let start = std::time::Instant::now();
1854 let mut idle_start: Option<std::time::Instant> = None;
1855 let idle_duration = std::time::Duration::from_millis(500); loop {
1858 if start.elapsed().as_millis() > timeout_ms as u128 {
1859 return Ok(());
1860 }
1861
1862 let current_inflight = self.network_monitor.get_inflight_count();
1863 tracing::debug!("Current active requests: {}", current_inflight);
1864
1865 if current_inflight <= max_inflight {
1866 let start_time = idle_start.get_or_insert_with(std::time::Instant::now);
1868
1869 if start_time.elapsed() >= idle_duration {
1871 return Ok(());
1872 }
1873 } else {
1874 idle_start = None;
1876 }
1877
1878 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1879 }
1880 }
1881
1882 pub async fn wait_for_timeout(&self, ms: u64) {
1895 tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
1896 }
1897
1898 pub async fn cleanup(&self) -> Result<()> {
1929 tracing::info!("Starting Page resource cleanup...");
1930
1931 self.response_monitor_manager.clear_monitors().await;
1933 tracing::debug!("Network monitors cleared");
1934
1935 self.lifecycle_callbacks.lock().await.clear();
1937 self.dom_mutation_callbacks.lock().await.clear();
1938 tracing::debug!("Lifecycle and DOM mutation callbacks cleared");
1939
1940 self.main_frame_cache.lock().await.take();
1942 self.frames_cache.lock().await.clear();
1943 self.frame_parent_map.lock().await.clear();
1944 self.frame_children_map.lock().await.clear();
1945 tracing::debug!("Frame cache cleared");
1946
1947 self.domain_manager.disable_all_domains().await?;
1949 tracing::debug!("All CDP Domains disabled");
1950
1951 tracing::info!("Page resource cleanup completed");
1952 Ok(())
1953 }
1954}
1955
1956impl Drop for Page {
1960 fn drop(&mut self) {
1961 tracing::debug!("Page Drop triggered, starting automatic cleanup...");
1962
1963 if let Ok(handle) = tokio::runtime::Handle::try_current() {
1966 let domain_manager = Arc::clone(&self.domain_manager);
1968 let response_monitor_manager = Arc::clone(&self.response_monitor_manager);
1969 let lifecycle_callbacks = Arc::clone(&self.lifecycle_callbacks);
1970 let dom_mutation_callbacks = Arc::clone(&self.dom_mutation_callbacks);
1971 let main_frame_cache = Arc::clone(&self.main_frame_cache);
1972 let frames_cache = Arc::clone(&self.frames_cache);
1973 let frame_parent_map = Arc::clone(&self.frame_parent_map);
1974 let frame_children_map = Arc::clone(&self.frame_children_map);
1975
1976 handle.spawn(async move {
1977 response_monitor_manager.clear_monitors().await;
1979
1980 lifecycle_callbacks.lock().await.clear();
1982 dom_mutation_callbacks.lock().await.clear();
1983
1984 main_frame_cache.lock().await.take();
1986 frames_cache.lock().await.clear();
1987 frame_parent_map.lock().await.clear();
1988 frame_children_map.lock().await.clear();
1989
1990 if let Err(e) = domain_manager.disable_all_domains().await {
1992 tracing::warn!("Failed to disable Domains during Page Drop: {:?}", e);
1993 }
1994
1995 tracing::debug!("Page automatic cleanup completed");
1996 });
1997 } else {
1998 tracing::warn!("Cannot perform async cleanup during Page Drop (not in tokio runtime)");
2000 tracing::warn!("Recommended to call page.cleanup().await explicitly");
2001 }
2002 }
2003}