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
128pub struct Page {
130 pub(crate) session: Arc<Session>,
131 pub contexts: Arc<Mutex<HashMap<String, u32>>>,
133 pub domain_manager: Arc<DomainManager>,
135 pub network_monitor: Arc<NetworkMonitor>,
137 pub response_monitor_manager: Arc<ResponseMonitorManager>,
139 main_frame_cache: Arc<Mutex<Option<Frame>>>,
141 frames_cache: Arc<Mutex<HashMap<String, Frame>>>,
143 frame_parent_map: Arc<Mutex<HashMap<String, String>>>,
145 frame_children_map: Arc<Mutex<HashMap<String, Vec<String>>>>,
147 lifecycle_callbacks: Arc<Mutex<Vec<FrameLifecycleCallback>>>,
149 dom_mutation_callbacks: Arc<Mutex<Vec<DomMutationCallback>>>,
151 tracing_state: Arc<Mutex<TracingSessionState>>,
153 pub(crate) file_chooser_lock: Arc<Mutex<()>>,
155}
156
157impl Page {
158 pub(crate) fn new_from_browser(session: Arc<Session>) -> Self {
160 let domain_manager = Arc::new(DomainManager::new(Arc::clone(&session)));
161 Self {
162 session,
163 contexts: Arc::new(Mutex::new(HashMap::new())),
164 domain_manager,
165 main_frame_cache: Arc::new(Mutex::new(None)),
166 frames_cache: Arc::new(Mutex::new(HashMap::new())),
167 frame_parent_map: Arc::new(Mutex::new(HashMap::new())),
168 frame_children_map: Arc::new(Mutex::new(HashMap::new())),
169 lifecycle_callbacks: Arc::new(Mutex::new(Vec::new())),
170 dom_mutation_callbacks: Arc::new(Mutex::new(Vec::new())),
171 network_monitor: Arc::new(NetworkMonitor::default()),
172 response_monitor_manager: Arc::new(ResponseMonitorManager::default()),
173 tracing_state: Arc::new(Mutex::new(TracingSessionState::default())),
174 file_chooser_lock: Arc::new(Mutex::new(())),
175 }
176 }
177
178 pub async fn connect_to_active_page(
180 port: Option<u16>,
181 pattern: Option<&str>,
182 ) -> Result<Arc<Self>> {
183 let port = Self::resolve_remote_debug_port(port, None)?;
184 Self::connect_to_active_page_inner(port, pattern).await
185 }
186
187 pub async fn connect_to_active_page_with_browser(
189 port: Option<u16>,
190 pattern: Option<&str>,
191 browser_process: Option<&str>,
192 ) -> Result<Arc<Self>> {
193 let port = Self::resolve_remote_debug_port(port, browser_process)?;
194 Self::connect_to_active_page_inner(port, pattern).await
195 }
196
197 fn resolve_remote_debug_port(port: Option<u16>, browser_process: Option<&str>) -> Result<u16> {
198 if let Some(explicit) = port {
199 return Ok(explicit);
200 }
201
202 if let Some(process_name) = browser_process {
203 return find_running_browser_port_by_process_name(process_name);
204 }
205
206 for bt in [
207 BrowserType::Chrome,
208 BrowserType::Edge,
209 BrowserType::Chromium,
210 ] {
211 if let Ok(found) = find_running_browser_port(bt) {
212 return Ok(found);
213 }
214 }
215
216 Err(CdpError::tool(
217 "Found no running browser with remote debugging enabled, please specify browser type or port explicitly",
218 ))
219 }
220
221 async fn connect_to_active_page_inner(port: u16, pattern: Option<&str>) -> Result<Arc<Self>> {
222 let page_ws_url = resolve_active_page_ws_url(port, pattern).await?;
223 let (ws_stream, _) = connect_async(page_ws_url.as_str()).await?;
224 let (writer, reader) = ws_stream.split();
225 let (event_sender, event_receiver) = mpsc::channel(crate::DEFAULT_CHANNEL_CAPACITY);
227 let connection = Arc::new(ConnectionInternals::new(writer));
228
229 let session = Session::new(None, connection.clone(), event_receiver);
230 let session = Arc::new(session);
231 let domain_manager = Arc::new(DomainManager::new(Arc::clone(&session)));
232
233 let page = Arc::new(Page {
234 session,
235 contexts: Arc::new(Mutex::new(HashMap::new())),
236 domain_manager,
237 main_frame_cache: Arc::new(Mutex::new(None)),
238 frames_cache: Arc::new(Mutex::new(HashMap::new())),
239 frame_parent_map: Arc::new(Mutex::new(HashMap::new())),
240 frame_children_map: Arc::new(Mutex::new(HashMap::new())),
241 lifecycle_callbacks: Arc::new(Mutex::new(Vec::new())),
242 dom_mutation_callbacks: Arc::new(Mutex::new(Vec::new())),
243 network_monitor: Arc::new(NetworkMonitor::default()),
244 response_monitor_manager: Arc::new(ResponseMonitorManager::default()),
245 tracing_state: Arc::new(Mutex::new(TracingSessionState::default())),
246 file_chooser_lock: Arc::new(Mutex::new(())),
247 });
248
249 let page_weak = Arc::downgrade(&page);
251
252 tokio::spawn(direct_message_dispatcher(
254 reader,
255 connection.clone(),
256 event_sender,
257 page_weak,
258 ));
259
260 page.domain_manager.enable_required_domains().await?;
262
263 Ok(page)
264 }
265
266 pub fn mouse(&self) -> Mouse {
268 Mouse::new(Arc::clone(&self.session), Arc::clone(&self.domain_manager))
269 }
270
271 pub fn keyboard(&self) -> Keyboard {
273 Keyboard::new(Arc::clone(&self.session), Arc::clone(&self.domain_manager))
274 }
275
276 pub fn accessibility(self: &Arc<Self>) -> AccessibilityController {
277 AccessibilityController::new(Arc::clone(self))
278 }
279
280 pub fn emulation(self: &Arc<Self>) -> EmulationController {
281 EmulationController::new(Arc::clone(self))
282 }
283
284 pub fn tracing(self: &Arc<Self>) -> TracingController {
285 TracingController::new(Arc::clone(self), Arc::clone(&self.tracing_state))
286 }
287
288 pub async fn navigate(&self, url: &str) -> Result<NavigateReturnObject> {
289 let navigate = Navigate {
290 url: url.to_string(),
291 referrer: None,
292 transition_type: None,
293 frame_id: None,
294 referrer_policy: None,
295 };
296 self.session
297 .send_command::<_, NavigateReturnObject>(navigate, None)
298 .await
299 }
300
301 fn events(&self) -> broadcast::Receiver<CdpEvent> {
302 self.session.event_bus.subscribe()
303 }
304
305 pub fn on<E>(&self) -> Pin<Box<dyn Stream<Item = E> + Send + 'static>>
306 where
307 E: TryFrom<CdpEvent> + Send + 'static,
308 <E as TryFrom<CdpEvent>>::Error: Send,
309 {
310 let stream = BroadcastStream::new(self.events())
311 .filter_map(|result| async { result.ok() })
312 .filter_map(|event| async { E::try_from(event).ok() });
313 Box::pin(stream)
314 }
315
316 pub(crate) async fn wait_for<E>(&self) -> Result<E>
317 where
318 E: TryFrom<CdpEvent> + Send + 'static,
319 <E as TryFrom<CdpEvent>>::Error: Send,
320 {
321 self.on::<E>().next().await.ok_or_else(|| {
322 CdpError::page("Event stream closed before event was received.".to_string())
323 })
324 }
325
326 pub async fn wait_for_loaded(&self) -> Result<page_cdp::events::LoadEventFiredEvent> {
327 self.wait_for::<page_cdp::events::LoadEventFiredEvent>()
328 .await
329 }
330
331 pub async fn get_title(&self) -> Result<String> {
332 let method = Evaluate {
333 expression: "document.title".to_string(),
334 object_group: None,
335 include_command_line_api: None,
336 silent: None,
337 context_id: None,
338 return_by_value: None,
339 generate_preview: None,
340 user_gesture: None,
341 await_promise: None,
342 throw_on_side_effect: None,
343 timeout: None,
344 disable_breaks: None,
345 repl_mode: None,
346 allow_unsafe_eval_blocked_by_csp: None,
347 unique_context_id: None,
348 serialization_options: None,
349 };
350 let eval_result = self
351 .session
352 .send_command::<_, EvaluateReturnObject>(method, None)
353 .await?;
354
355 let title = eval_result
357 .result
358 .value
359 .and_then(|v| v.as_str().map(ToString::to_string))
360 .ok_or_else(|| {
361 CdpError::page("Could not extract title string from evaluation result".to_string())
362 })?;
363
364 Ok(title)
365 }
366
367 pub async fn main_frame(self: &Arc<Self>) -> Result<Frame> {
369 {
370 let cache = self.main_frame_cache.lock().await;
371 if let Some(frame) = cache.as_ref() {
372 return Ok(frame.clone());
373 }
374 }
375 let method = GetFrameTree(None);
376 let obj = self
377 .session
378 .send_command::<_, GetFrameTreeReturnObject>(method, None)
379 .await?;
380 let main_frame_id = obj.frame_tree.frame.id.clone();
381 let main_frame = Frame::new(main_frame_id.clone(), Arc::clone(self));
382 {
383 let mut cache = self.main_frame_cache.lock().await;
384 *cache = Some(main_frame.clone());
385 }
386 {
387 let mut frames = self.frames_cache.lock().await;
388 frames.insert(main_frame_id, main_frame.clone());
389 }
390
391 Ok(main_frame)
392 }
393
394 pub async fn query_selector(self: &Arc<Self>, selector: &str) -> Result<Option<ElementHandle>> {
419 let main_frame = self.main_frame().await?;
421 match main_frame.query_selector(selector).await {
422 Ok(Some(element)) => return Ok(Some(element)),
423 Ok(None) => {} Err(e) => {
425 eprintln!("Warning: Failed to query in main frame: {}", e);
427 }
428 }
429
430 let frames = self.all_frames().await?;
432 for frame in frames.iter().skip(1) {
433 match frame.query_selector(selector).await {
435 Ok(Some(element)) => return Ok(Some(element)),
436 Ok(None) => continue, Err(e) => {
438 eprintln!("Warning: Failed to query in frame {}: {}", frame.id(), e);
440 continue;
441 }
442 }
443 }
444
445 Ok(None)
446 }
447
448 pub async fn query_selector_all(
477 self: &Arc<Self>,
478 selector: &str,
479 ) -> Result<Vec<ElementHandle>> {
480 let frames = self.all_frames().await?;
482 let mut all_elements = Vec::new();
483
484 for frame in frames {
486 match frame.query_selector_all(selector).await {
487 Ok(elements) => {
488 all_elements.extend(elements);
489 }
490 Err(e) => {
491 eprintln!("Warning: Failed to query in frame {}: {}", frame.id(), e);
493 continue;
494 }
495 }
496 }
497
498 Ok(all_elements)
499 }
500
501 pub async fn all_frames(self: &Arc<Self>) -> Result<Vec<Frame>> {
503 let method = GetFrameTree(None);
504 let result = self
505 .session
506 .send_command::<_, GetFrameTreeReturnObject>(method, None)
507 .await?;
508 let mut all_frames = Vec::new();
509 let mut new_cache = HashMap::new();
510 let mut new_parent_map = HashMap::new();
511 let mut new_children_map = HashMap::new();
512
513 Self::collect_frames_recursive(
514 &result.frame_tree,
515 None, Arc::clone(self),
517 &mut all_frames,
518 &mut new_cache,
519 &mut new_parent_map,
520 &mut new_children_map,
521 );
522
523 {
525 let mut frames_cache = self.frames_cache.lock().await;
526 *frames_cache = new_cache;
527 }
528
529 {
530 let mut parent_map = self.frame_parent_map.lock().await;
531 *parent_map = new_parent_map;
532 }
533
534 {
535 let mut children_map = self.frame_children_map.lock().await;
536 *children_map = new_children_map;
537 }
538
539 {
540 let mut main_cache = self.main_frame_cache.lock().await;
541 if main_cache.is_none() && !all_frames.is_empty() {
542 *main_cache = Some(all_frames[0].clone());
543 }
544 }
545
546 Ok(all_frames)
547 }
548
549 fn collect_frames_recursive(
551 frame_tree: &FrameTree,
552 parent_id: Option<String>,
553 page_arc: Arc<Page>,
554 frames_list: &mut Vec<Frame>,
555 frames_map: &mut HashMap<String, Frame>,
556 parent_map: &mut HashMap<String, String>,
557 children_map: &mut HashMap<String, Vec<String>>,
558 ) {
559 let frame_id = frame_tree.frame.id.clone();
560
561 let current_frame = Frame::new(frame_id.clone(), Arc::clone(&page_arc));
563
564 frames_list.push(current_frame.clone());
566 frames_map.insert(frame_id.clone(), current_frame);
567
568 if let Some(ref parent) = parent_id {
570 parent_map.insert(frame_id.clone(), parent.clone());
571 children_map
572 .entry(parent.clone())
573 .or_default()
574 .push(frame_id.clone());
575 }
576
577 if let Some(child_frames) = &frame_tree.child_frames {
579 for child_tree in child_frames {
580 Self::collect_frames_recursive(
581 child_tree,
582 Some(frame_id.clone()),
583 Arc::clone(&page_arc),
584 frames_list,
585 frames_map,
586 parent_map,
587 children_map,
588 );
589 }
590 }
591 }
592
593 pub async fn get_frame(&self, frame_id: &str) -> Option<Frame> {
595 self.frames_cache.lock().await.get(frame_id).cloned()
596 }
597
598 pub async fn clear_frame_cache(&self) {
600 self.main_frame_cache.lock().await.take();
601 self.frames_cache.lock().await.clear();
602 }
603
604 pub async fn register_execution_context(&self, frame_id: String, context_id: u32) {
606 self.contexts.lock().await.insert(frame_id, context_id);
607 }
608
609 pub async fn remove_execution_context(&self, context_id: u32) {
611 let mut contexts = self.contexts.lock().await;
612 contexts.retain(|_frame_id, &mut ctx_id| ctx_id != context_id);
614 }
615
616 pub async fn clear_execution_contexts(&self) {
618 self.contexts.lock().await.clear();
619 }
620
621 pub async fn get_parent_frame(&self, frame_id: &str) -> Option<Frame> {
625 let parent_map = self.frame_parent_map.lock().await;
626 let parent_id = parent_map.get(frame_id)?;
627 self.get_frame(parent_id).await
628 }
629
630 pub async fn get_child_frames(&self, frame_id: &str) -> Vec<Frame> {
632 let children_map = self.frame_children_map.lock().await;
633 let child_ids = children_map.get(frame_id);
634
635 match child_ids {
636 Some(ids) => {
637 let mut children = Vec::new();
638 for id in ids {
639 if let Some(frame) = self.get_frame(id).await {
640 children.push(frame);
641 }
642 }
643 children
644 }
645 None => Vec::new(),
646 }
647 }
648
649 pub async fn get_ancestor_frames(&self, frame_id: &str) -> Vec<Frame> {
651 let mut ancestors = Vec::new();
652 let mut current_id = frame_id.to_string();
653
654 loop {
655 let parent = self.get_parent_frame(¤t_id).await;
656 match parent {
657 Some(frame) => {
658 current_id = frame.id.clone();
659 ancestors.push(frame);
660 }
661 None => break,
662 }
663 }
664
665 ancestors
666 }
667
668 pub async fn get_descendant_frames(&self, frame_id: &str) -> Vec<Frame> {
670 let mut descendants = Vec::new();
671 self.collect_descendants_recursive(frame_id, &mut descendants)
672 .await;
673 descendants
674 }
675
676 fn collect_descendants_recursive<'a>(
678 &'a self,
679 frame_id: &'a str,
680 result: &'a mut Vec<Frame>,
681 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 'a>> {
682 Box::pin(async move {
683 let children = self.get_child_frames(frame_id).await;
684 for child in children {
685 result.push(child.clone());
686 self.collect_descendants_recursive(&child.id, result).await;
687 }
688 })
689 }
690
691 pub async fn query_frame(self: &Arc<Self>, selector: &str) -> Result<Option<Frame>> {
719 let frames = self.all_frames().await?;
720
721 for frame in frames {
722 if self.matches_selector(&frame, selector).await? {
723 return Ok(Some(frame));
724 }
725 }
726
727 Ok(None)
728 }
729
730 pub async fn query_frames(self: &Arc<Self>, selector: &str) -> Result<Vec<Frame>> {
732 let frames = self.all_frames().await?;
733 let mut matched = Vec::new();
734
735 for frame in frames {
736 if self.matches_selector(&frame, selector).await? {
737 matched.push(frame);
738 }
739 }
740
741 Ok(matched)
742 }
743
744 async fn matches_selector(&self, frame: &Frame, selector: &str) -> Result<bool> {
746 if let Some(name_pattern) = selector.strip_prefix("name:") {
747 let frame_name = frame.name().await?;
748 return Ok(frame_name.as_deref() == Some(name_pattern));
749 }
750
751 if let Some(url_prefix) = selector.strip_prefix("url:") {
752 let frame_url = frame.url().await?;
753 return Ok(frame_url.starts_with(url_prefix));
754 }
755
756 if let Some(url_pattern) = selector.strip_prefix("url~") {
757 let frame_url = frame.url().await?;
758 return Ok(frame_url.contains(url_pattern));
760 }
761
762 if let Some(depth_str) = selector.strip_prefix("depth:")
763 && let Ok(target_depth) = depth_str.parse::<usize>()
764 {
765 let ancestors = self.get_ancestor_frames(&frame.id).await;
766 return Ok(ancestors.len() == target_depth);
767 }
768
769 Ok(false)
770 }
771
772 pub async fn on_frame_lifecycle(&self, callback: FrameLifecycleCallback) {
798 self.lifecycle_callbacks.lock().await.push(callback);
799 }
800
801 pub(crate) async fn trigger_lifecycle_event(&self, event: FrameLifecycleEvent) {
803 let callbacks = self.lifecycle_callbacks.lock().await;
804 for callback in callbacks.iter() {
805 callback(event.clone());
806 }
807 }
808
809 pub(crate) async fn handle_frame_attached(
811 &self,
812 frame_id: String,
813 parent_frame_id: Option<String>,
814 ) {
815 if let Some(ref parent_id) = parent_frame_id {
817 self.frame_parent_map
818 .lock()
819 .await
820 .insert(frame_id.clone(), parent_id.clone());
821
822 self.frame_children_map
823 .lock()
824 .await
825 .entry(parent_id.clone())
826 .or_insert_with(Vec::new)
827 .push(frame_id.clone());
828 }
829
830 self.trigger_lifecycle_event(FrameLifecycleEvent::Attached {
832 frame_id,
833 parent_frame_id,
834 })
835 .await;
836 }
837
838 pub(crate) async fn handle_frame_detached(&self, frame_id: String) {
840 self.frames_cache.lock().await.remove(&frame_id);
842
843 let parent_id = self.frame_parent_map.lock().await.remove(&frame_id);
845 if let Some(parent) = parent_id {
846 let mut children_map = self.frame_children_map.lock().await;
847 if let Some(children) = children_map.get_mut(&parent) {
848 children.retain(|id| id != &frame_id);
849 }
850 }
851 self.frame_children_map.lock().await.remove(&frame_id);
852
853 self.trigger_lifecycle_event(FrameLifecycleEvent::Detached { frame_id })
855 .await;
856 }
857
858 pub(crate) async fn handle_frame_navigated(&self, frame_id: String, url: String) {
860 self.frames_cache.lock().await.remove(&frame_id);
862
863 self.trigger_lifecycle_event(FrameLifecycleEvent::Navigated { frame_id, url })
865 .await;
866 }
867
868 pub async fn on_dom_mutation(&self, callback: DomMutationCallback) {
897 self.dom_mutation_callbacks.lock().await.push(callback);
898 }
899
900 pub async fn enable_dom_mutations(&self) -> Result<()> {
902 use cdp_protocol::dom;
903
904 let enable = dom::Enable {
906 include_whitespace: None,
907 };
908 self.session
909 .send_command::<_, dom::EnableReturnObject>(enable, None)
910 .await?;
911
912 println!("DOM mutation monitoring enabled");
913 Ok(())
914 }
915
916 pub(crate) async fn trigger_dom_mutation_event(&self, event: DomMutationEvent) {
918 let callbacks = self.dom_mutation_callbacks.lock().await;
919 for callback in callbacks.iter() {
920 callback(event.clone());
921 }
922 }
923
924 pub(crate) async fn handle_child_node_inserted(
926 &self,
927 parent_node_id: u32,
928 previous_node_id: u32,
929 node: serde_json::Value,
930 ) {
931 self.trigger_dom_mutation_event(DomMutationEvent::ChildNodeInserted {
932 parent_node_id,
933 previous_node_id,
934 node,
935 })
936 .await;
937 }
938
939 pub(crate) async fn handle_child_node_removed(&self, parent_node_id: u32, node_id: u32) {
941 self.trigger_dom_mutation_event(DomMutationEvent::ChildNodeRemoved {
942 parent_node_id,
943 node_id,
944 })
945 .await;
946 }
947
948 pub(crate) async fn handle_attribute_modified(
950 &self,
951 node_id: u32,
952 name: String,
953 value: String,
954 ) {
955 self.trigger_dom_mutation_event(DomMutationEvent::AttributeModified {
956 node_id,
957 name,
958 value,
959 })
960 .await;
961 }
962
963 pub(crate) async fn handle_attribute_removed(&self, node_id: u32, name: String) {
965 self.trigger_dom_mutation_event(DomMutationEvent::AttributeRemoved { node_id, name })
966 .await;
967 }
968
969 pub(crate) async fn handle_character_data_modified(
971 &self,
972 node_id: u32,
973 character_data: String,
974 ) {
975 self.trigger_dom_mutation_event(DomMutationEvent::CharacterDataModified {
976 node_id,
977 character_data,
978 })
979 .await;
980 }
981}
982
983#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
987pub struct FrameSnapshot {
988 pub frame_id: String,
990 pub url: String,
992 pub name: Option<String>,
994 pub parent_frame_id: Option<String>,
996 pub child_frame_ids: Vec<String>,
998 pub timestamp: u64,
1000 pub html_content: Option<String>,
1002 pub dom_depth: Option<usize>,
1004}
1005
1006impl Page {
1007 pub async fn create_frame_snapshot(
1031 &self,
1032 frame_id: &str,
1033 include_html: bool,
1034 ) -> Result<FrameSnapshot> {
1035 let frame = self
1036 .get_frame(frame_id)
1037 .await
1038 .ok_or_else(|| CdpError::page(format!("Frame not found: {}", frame_id)))?;
1039
1040 let url = frame.url().await.unwrap_or_else(|_| "".to_string());
1041 let name = frame.name().await.unwrap_or(None);
1042 let parent_frame_id = self.frame_parent_map.lock().await.get(frame_id).cloned();
1043 let child_frame_ids = self
1044 .frame_children_map
1045 .lock()
1046 .await
1047 .get(frame_id)
1048 .cloned()
1049 .unwrap_or_default();
1050
1051 let html_content = if include_html {
1052 match frame.evaluate("document.documentElement.outerHTML").await {
1053 Ok(html_value) => html_value.as_str().map(|s| s.to_string()),
1054 Err(_) => None,
1055 }
1056 } else {
1057 None
1058 };
1059
1060 let dom_depth = if include_html {
1061 match frame
1062 .evaluate(
1063 r#"
1064 (function() {
1065 function getDepth(element) {
1066 if (!element || !element.children) return 0;
1067 let maxDepth = 0;
1068 for (let child of element.children) {
1069 maxDepth = Math.max(maxDepth, getDepth(child));
1070 }
1071 return maxDepth + 1;
1072 }
1073 return getDepth(document.documentElement);
1074 })()
1075 "#,
1076 )
1077 .await
1078 {
1079 Ok(depth_value) => depth_value.as_u64().map(|d| d as usize),
1080 Err(_) => None,
1081 }
1082 } else {
1083 None
1084 };
1085
1086 let timestamp = std::time::SystemTime::now()
1087 .duration_since(std::time::UNIX_EPOCH)
1088 .unwrap_or_default()
1089 .as_secs();
1090
1091 Ok(FrameSnapshot {
1092 frame_id: frame_id.to_string(),
1093 url,
1094 name,
1095 parent_frame_id,
1096 child_frame_ids,
1097 timestamp,
1098 html_content,
1099 dom_depth,
1100 })
1101 }
1102
1103 pub async fn create_all_frames_snapshot(
1120 self: &Arc<Self>,
1121 include_html: bool,
1122 ) -> Result<Vec<FrameSnapshot>> {
1123 let frames = self.all_frames().await?;
1124 let mut snapshots = Vec::new();
1125
1126 for frame in frames {
1127 match self.create_frame_snapshot(&frame.id, include_html).await {
1128 Ok(snapshot) => snapshots.push(snapshot),
1129 Err(e) => {
1130 eprintln!("Failed to create snapshot for frame {}: {}", frame.id, e);
1131 }
1132 }
1133 }
1134
1135 Ok(snapshots)
1136 }
1137
1138 pub fn compare_snapshots(snapshot1: &FrameSnapshot, snapshot2: &FrameSnapshot) -> Vec<String> {
1160 let mut differences = Vec::new();
1161
1162 if snapshot1.frame_id != snapshot2.frame_id {
1163 differences.push(format!(
1164 "Frame ID changed: {} -> {}",
1165 snapshot1.frame_id, snapshot2.frame_id
1166 ));
1167 }
1168
1169 if snapshot1.url != snapshot2.url {
1170 differences.push(format!(
1171 "URL changed: {} -> {}",
1172 snapshot1.url, snapshot2.url
1173 ));
1174 }
1175
1176 if snapshot1.name != snapshot2.name {
1177 differences.push(format!(
1178 "Name changed: {:?} -> {:?}",
1179 snapshot1.name, snapshot2.name
1180 ));
1181 }
1182
1183 if snapshot1.parent_frame_id != snapshot2.parent_frame_id {
1184 differences.push(format!(
1185 "Parent Frame ID changed: {:?} -> {:?}",
1186 snapshot1.parent_frame_id, snapshot2.parent_frame_id
1187 ));
1188 }
1189
1190 if snapshot1.child_frame_ids != snapshot2.child_frame_ids {
1191 differences.push(format!(
1192 "Child Frames changed: {} -> {}",
1193 snapshot1.child_frame_ids.len(),
1194 snapshot2.child_frame_ids.len()
1195 ));
1196 }
1197
1198 if let (Some(html1), Some(html2)) = (&snapshot1.html_content, &snapshot2.html_content)
1199 && html1 != html2
1200 {
1201 differences.push(format!(
1202 "HTML content changed: {} bytes -> {} bytes",
1203 html1.len(),
1204 html2.len()
1205 ));
1206 }
1207
1208 if snapshot1.dom_depth != snapshot2.dom_depth {
1209 differences.push(format!(
1210 "DOM depth changed: {:?} -> {:?}",
1211 snapshot1.dom_depth, snapshot2.dom_depth
1212 ));
1213 }
1214
1215 differences
1216 }
1217
1218 pub async fn type_text(&self, text: &str) -> Result<()> {
1242 self.keyboard().insert_text(text).await
1243 }
1244
1245 pub async fn type_text_with_delay(
1271 &self,
1272 text: &str,
1273 min_delay_ms: u64,
1274 max_delay_ms: u64,
1275 ) -> Result<()> {
1276 self.keyboard()
1277 .type_text_with_delay(text, min_delay_ms, max_delay_ms)
1278 .await
1279 }
1280
1281 async fn get_device_pixel_ratio(&self) -> Result<f64> {
1286 let eval_result = self
1287 .session
1288 .send_command::<_, EvaluateReturnObject>(
1289 Evaluate {
1290 expression: "window.devicePixelRatio".to_string(),
1291 object_group: None,
1292 include_command_line_api: None,
1293 silent: None,
1294 context_id: None,
1295 return_by_value: Some(true),
1296 generate_preview: None,
1297 user_gesture: None,
1298 await_promise: None,
1299 throw_on_side_effect: None,
1300 timeout: None,
1301 disable_breaks: None,
1302 repl_mode: None,
1303 allow_unsafe_eval_blocked_by_csp: None,
1304 unique_context_id: None,
1305 serialization_options: None,
1306 },
1307 None,
1308 )
1309 .await?;
1310
1311 let dpr = eval_result
1312 .result
1313 .value
1314 .and_then(|v| v.as_f64())
1315 .unwrap_or(1.0);
1316
1317 Ok(dpr)
1318 }
1319
1320 pub async fn screenshot(&self, full_page: bool, save_path: Option<PathBuf>) -> Result<String> {
1347 self.screenshot_with_options(full_page, save_path, true)
1348 .await
1349 }
1350
1351 pub async fn screenshot_with_options(
1377 &self,
1378 full_page: bool,
1379 save_path: Option<PathBuf>,
1380 auto_resolve_dpr: bool,
1381 ) -> Result<String> {
1382 use base64::Engine;
1383 use cdp_protocol::page as page_cdp;
1384
1385 let device_scale = if auto_resolve_dpr {
1387 let dpr = self.get_device_pixel_ratio().await?;
1388 dpr.clamp(0.5, 3.0)
1390 } else {
1391 1.0
1392 };
1393
1394 let mut clip: Option<Viewport> = None;
1395 let layout_metrics_params = page_cdp::GetLayoutMetrics(None);
1396 let layout_metrics: page_cdp::GetLayoutMetricsReturnObject = self
1397 .session
1398 .send_command(layout_metrics_params, None)
1399 .await?;
1400 let visual_viewport_scale = layout_metrics.visual_viewport.scale;
1401 let device_scale_factor = device_scale.max(visual_viewport_scale);
1402 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
1403
1404 if let Some(mut c) = clip.as_ref().cloned() {
1405 let dpr = c.scale.max(1.0);
1406 let to_device = |v: f64| (v * dpr).round();
1407 let x_dev = to_device(c.x);
1408 let y_dev = to_device(c.y);
1409 let x_px = (x_dev.floor() - 1.0).max(0.0);
1410 let y_px = (y_dev.floor() - 1.0).max(0.0);
1411 let right_px = to_device(c.x + c.width).ceil();
1412 let bottom_px = to_device(c.y + c.height).ceil();
1413 let w_px = (right_px - x_px).max(1.0);
1414 let h_px = (bottom_px - y_px).max(1.0);
1415 c.x = x_px / dpr;
1416 c.y = y_px / dpr;
1417 c.width = w_px / dpr;
1418 c.height = h_px / dpr;
1419 c.scale = device_scale_factor;
1420 clip = Some(c);
1421 }
1422
1423 let screenshot_params = page_cdp::CaptureScreenshot {
1425 format: Some(page_cdp::CaptureScreenshotFormatOption::Png),
1426 quality: None,
1427 clip,
1428 from_surface: Some(true),
1429 capture_beyond_viewport: Some(full_page),
1430 optimize_for_speed: None,
1431 };
1432
1433 let result: page_cdp::CaptureScreenshotReturnObject =
1434 self.session.send_command(screenshot_params, None).await?;
1435
1436 let out_path_buf: std::path::PathBuf = match save_path {
1438 Some(pv) => {
1439 if pv.parent().is_none_or(|p| p.as_os_str().is_empty()) {
1440 std::env::current_dir()?.join(pv)
1441 } else {
1442 if let Some(parent) = pv.parent() {
1443 std::fs::create_dir_all(parent)?;
1444 }
1445 pv.to_path_buf()
1446 }
1447 }
1448 None => {
1449 let out_dir = std::env::var("OUT_PATH").unwrap_or_else(|_| ".".to_string());
1450 let dir = std::path::PathBuf::from(out_dir);
1451 std::fs::create_dir_all(&dir)?;
1452 let nanos = std::time::SystemTime::now()
1453 .duration_since(std::time::UNIX_EPOCH)
1454 .unwrap_or_default()
1455 .as_nanos();
1456 dir.join(format!("screenshot-{}.png", nanos))
1457 }
1458 };
1459
1460 let bytes = base64::engine::general_purpose::STANDARD
1462 .decode(&result.data)
1463 .map_err(|err| {
1464 CdpError::page(format!("Failed to decode screenshot response: {}", err))
1465 })?;
1466 let mut f = File::create(&out_path_buf).await?;
1467 f.write_all(&bytes).await?;
1468 f.flush().await?;
1469
1470 let out_path = out_path_buf.to_string_lossy();
1471 Ok(out_path.into_owned())
1472 }
1473
1474 pub async fn wait_for_selector(
1506 self: &Arc<Self>,
1507 selector: &str,
1508 options: Option<WaitForSelectorOptions>,
1509 ) -> Result<ElementHandle> {
1510 let opts = options.unwrap_or_default();
1511 let timeout_ms = opts.timeout_ms.unwrap_or(30000);
1512 let visible = opts.visible.unwrap_or(false);
1513 let hidden = opts.hidden.unwrap_or(false);
1514
1515 let start = std::time::Instant::now();
1516 let poll_interval = std::time::Duration::from_millis(100);
1517
1518 loop {
1519 if start.elapsed().as_millis() > timeout_ms as u128 {
1521 return Err(CdpError::page(format!(
1522 "Timeout waiting for selector '{}' ({}ms)",
1523 selector, timeout_ms
1524 )));
1525 }
1526
1527 if let Some(element) = self.query_selector(selector).await? {
1529 if visible
1531 && let Ok(is_visible) = element.is_visible().await
1532 && !is_visible
1533 {
1534 tokio::time::sleep(poll_interval).await;
1535 continue;
1536 }
1537
1538 if hidden
1540 && let Ok(is_visible) = element.is_visible().await
1541 && is_visible
1542 {
1543 tokio::time::sleep(poll_interval).await;
1544 continue;
1545 }
1546
1547 return Ok(element);
1548 }
1549
1550 tokio::time::sleep(poll_interval).await;
1551 }
1552 }
1553
1554 pub async fn wait_for_selector_hidden(
1571 self: &Arc<Self>,
1572 selector: &str,
1573 timeout_ms: Option<u64>,
1574 ) -> Result<()> {
1575 let timeout = timeout_ms.unwrap_or(30000);
1576 let start = std::time::Instant::now();
1577 let poll_interval = std::time::Duration::from_millis(100);
1578
1579 loop {
1580 if start.elapsed().as_millis() > timeout as u128 {
1581 return Err(CdpError::page(format!(
1582 "Timeout waiting for selector '{}' to be hidden ({}ms)",
1583 selector, timeout
1584 )));
1585 }
1586
1587 match self.query_selector(selector).await? {
1588 None => return Ok(()), Some(element) => {
1590 if let Ok(is_visible) = element.is_visible().await
1592 && !is_visible
1593 {
1594 return Ok(());
1595 }
1596 }
1597 }
1598
1599 tokio::time::sleep(poll_interval).await;
1600 }
1601 }
1602
1603 pub async fn wait_for_function(
1632 self: &Arc<Self>,
1633 function: &str,
1634 timeout_ms: Option<u64>,
1635 poll_interval_ms: Option<u64>,
1636 ) -> Result<()> {
1637 let timeout = timeout_ms.unwrap_or(30000);
1638 let poll_interval = std::time::Duration::from_millis(poll_interval_ms.unwrap_or(100));
1639 let start = std::time::Instant::now();
1640
1641 let script = format!(
1643 r#"
1644 (() => {{
1645 try {{
1646 const candidate = ({});
1647 const value = typeof candidate === 'function' ? candidate() : candidate;
1648 if (value && typeof value.then === 'function') {{
1649 return value.then(Boolean).catch(() => false);
1650 }}
1651 return Boolean(value);
1652 }} catch (error) {{
1653 return false;
1654 }}
1655 }})()
1656 "#,
1657 function
1658 );
1659
1660 loop {
1661 if start.elapsed().as_millis() > timeout as u128 {
1662 return Err(CdpError::page(format!(
1663 "Timeout waiting for function ({}ms)",
1664 timeout
1665 )));
1666 }
1667
1668 let main_frame = match self.main_frame().await {
1670 Ok(frame) => frame,
1671 Err(_) => {
1672 tokio::time::sleep(poll_interval).await;
1673 continue;
1674 }
1675 };
1676
1677 match main_frame.evaluate(&script).await {
1678 Ok(result) => {
1679 if let Some(true) = result.as_bool() {
1680 return Ok(());
1681 }
1682 }
1683 Err(_) => {
1684 }
1686 }
1687
1688 tokio::time::sleep(poll_interval).await;
1689 }
1690 }
1691
1692 pub async fn wait_for_navigation(
1726 self: &Arc<Self>,
1727 options: Option<WaitForNavigationOptions>,
1728 ) -> Result<()> {
1729 let opts = options.unwrap_or_default();
1730 let timeout = opts.timeout_ms.unwrap_or(60000);
1731 let wait_until = opts.wait_until.unwrap_or(WaitUntil::NetworkIdle2);
1732
1733 match wait_until {
1734 WaitUntil::Load => {
1735 self.wait_for_function(
1737 "() => document.readyState === 'complete'",
1738 Some(timeout),
1739 None,
1740 )
1741 .await
1742 }
1743 WaitUntil::DOMContentLoaded => {
1744 self.wait_for_function(
1746 "() => document.readyState === 'interactive'",
1747 Some(timeout),
1748 None,
1749 )
1750 .await
1751 }
1752 WaitUntil::NetworkIdle0 => {
1753 self.wait_for_network_idle(timeout, 0).await
1755 }
1756 WaitUntil::NetworkIdle2 => {
1757 self.wait_for_network_idle(timeout, 2).await
1759 }
1760 }
1761 }
1762
1763 pub async fn on_network(&self, callback: NetworkEventCallback) {
1790 self.network_monitor.add_callback(callback).await;
1791 }
1792
1793 pub fn get_inflight_requests_count(&self) -> usize {
1808 self.network_monitor.get_inflight_count()
1809 }
1810
1811 async fn wait_for_network_idle(
1820 self: &Arc<Self>,
1821 timeout_ms: u64,
1822 max_inflight: usize,
1823 ) -> Result<()> {
1824 if !self.domain_manager.is_enabled(DomainType::Network).await {
1826 self.domain_manager.enable_network_domain().await?;
1827 }
1828
1829 self.network_monitor.reset_inflight().await;
1831
1832 let start = std::time::Instant::now();
1834 let mut idle_start: Option<std::time::Instant> = None;
1835 let idle_duration = std::time::Duration::from_millis(500); loop {
1838 if start.elapsed().as_millis() > timeout_ms as u128 {
1839 return Ok(());
1840 }
1841
1842 let current_inflight = self.network_monitor.get_inflight_count();
1843 tracing::debug!("Current active requests: {}", current_inflight);
1844
1845 if current_inflight <= max_inflight {
1846 let start_time = idle_start.get_or_insert_with(std::time::Instant::now);
1848
1849 if start_time.elapsed() >= idle_duration {
1851 return Ok(());
1852 }
1853 } else {
1854 idle_start = None;
1856 }
1857
1858 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
1859 }
1860 }
1861
1862 pub async fn wait_for_timeout(&self, ms: u64) {
1875 tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
1876 }
1877
1878 pub async fn cleanup(&self) -> Result<()> {
1909 tracing::info!("Starting Page resource cleanup...");
1910
1911 self.response_monitor_manager.clear_monitors().await;
1913 tracing::debug!("Network monitors cleared");
1914
1915 self.lifecycle_callbacks.lock().await.clear();
1917 self.dom_mutation_callbacks.lock().await.clear();
1918 tracing::debug!("Lifecycle and DOM mutation callbacks cleared");
1919
1920 self.main_frame_cache.lock().await.take();
1922 self.frames_cache.lock().await.clear();
1923 self.frame_parent_map.lock().await.clear();
1924 self.frame_children_map.lock().await.clear();
1925 tracing::debug!("Frame cache cleared");
1926
1927 self.domain_manager.disable_all_domains().await?;
1929 tracing::debug!("All CDP Domains disabled");
1930
1931 tracing::info!("Page resource cleanup completed");
1932 Ok(())
1933 }
1934}
1935
1936impl Drop for Page {
1940 fn drop(&mut self) {
1941 tracing::debug!("Page Drop triggered, starting automatic cleanup...");
1942
1943 if let Ok(handle) = tokio::runtime::Handle::try_current() {
1946 let domain_manager = Arc::clone(&self.domain_manager);
1948 let response_monitor_manager = Arc::clone(&self.response_monitor_manager);
1949 let lifecycle_callbacks = Arc::clone(&self.lifecycle_callbacks);
1950 let dom_mutation_callbacks = Arc::clone(&self.dom_mutation_callbacks);
1951 let main_frame_cache = Arc::clone(&self.main_frame_cache);
1952 let frames_cache = Arc::clone(&self.frames_cache);
1953 let frame_parent_map = Arc::clone(&self.frame_parent_map);
1954 let frame_children_map = Arc::clone(&self.frame_children_map);
1955
1956 handle.spawn(async move {
1957 response_monitor_manager.clear_monitors().await;
1959
1960 lifecycle_callbacks.lock().await.clear();
1962 dom_mutation_callbacks.lock().await.clear();
1963
1964 main_frame_cache.lock().await.take();
1966 frames_cache.lock().await.clear();
1967 frame_parent_map.lock().await.clear();
1968 frame_children_map.lock().await.clear();
1969
1970 if let Err(e) = domain_manager.disable_all_domains().await {
1972 tracing::warn!("Failed to disable Domains during Page Drop: {:?}", e);
1973 }
1974
1975 tracing::debug!("Page automatic cleanup completed");
1976 });
1977 } else {
1978 tracing::warn!("Cannot perform async cleanup during Page Drop (not in tokio runtime)");
1980 tracing::warn!("Recommended to call page.cleanup().await explicitly");
1981 }
1982 }
1983}