headless_chrome/browser/tab/
mod.rs

1use std::sync::{Arc, Mutex, RwLock, Weak};
2use std::thread;
3use std::time::Duration;
4use std::{
5    collections::HashMap,
6    sync::atomic::{AtomicBool, Ordering},
7};
8
9use anyhow::{Error, Result};
10
11use thiserror::Error;
12
13use log::{debug, error, info, trace, warn};
14
15use serde::de::DeserializeOwned;
16use serde::Serialize;
17use serde_json::{json, Value as Json};
18
19use dialog::Dialog;
20use element::Element;
21use point::Point;
22
23use crate::protocol::cdp::{
24    types::{Event, Method},
25    Browser, Debugger, Emulation, Fetch, Input, Log, Network, Page, Profiler, Runtime, Target, DOM,
26};
27
28use Runtime::AddBinding;
29
30use base64::Engine;
31
32use Input::DispatchKeyEvent;
33
34use Page::{AddScriptToEvaluateOnNewDocument, Navigate, SetInterceptFileChooserDialog};
35
36use Target::AttachToTarget;
37
38use DOM::{Node, NodeId};
39
40use Target::{TargetID, TargetInfo};
41
42use Log::ViolationSetting;
43
44use Fetch::{
45    events::RequestPausedEvent, AuthChallengeResponse, ContinueRequest, ContinueWithAuth,
46    FailRequest, FulfillRequest,
47};
48
49use Network::{
50    events::LoadingFailedEventParams, events::ResponseReceivedEventParams, Cookie, GetResponseBody,
51    GetResponseBodyReturnObject, SetExtraHTTPHeaders, SetUserAgentOverride,
52};
53
54use crate::util;
55
56use crate::types::{Bounds, CurrentBounds, PrintToPdfOptions, RemoteError};
57
58use super::transport::SessionId;
59use crate::browser::transport::Transport;
60use std::thread::sleep;
61
62pub mod dialog;
63pub mod element;
64mod keys;
65pub mod point;
66
67#[derive(Debug, Copy, Clone)]
68pub enum ModifierKey {
69    Alt = 1,
70    Ctrl = 2,
71    Meta = 4, // Meta/Command
72    Shift = 8,
73}
74
75#[derive(Debug)]
76pub enum RequestPausedDecision {
77    Fulfill(FulfillRequest),
78    Fail(FailRequest),
79    Continue(Option<ContinueRequest>),
80}
81
82#[rustfmt::skip]
83pub type ResponseHandler = Box<
84    dyn Fn(
85        ResponseReceivedEventParams,
86        &dyn Fn() -> Result<
87            GetResponseBodyReturnObject,
88            Error,
89        >,
90    ) + Send
91    + Sync,
92>;
93
94#[rustfmt::skip]
95pub type LoadingFailedHandler = Box<
96    dyn Fn(
97        ResponseReceivedEventParams,
98        LoadingFailedEventParams
99    ) + Send
100    + Sync,
101>;
102
103type SyncSendEvent = dyn EventListener<Event> + Send + Sync;
104pub trait RequestInterceptor {
105    fn intercept(
106        &self,
107        transport: Arc<Transport>,
108        session_id: SessionId,
109        event: RequestPausedEvent,
110    ) -> RequestPausedDecision;
111}
112
113impl<F> RequestInterceptor for F
114where
115    F: Fn(Arc<Transport>, SessionId, RequestPausedEvent) -> RequestPausedDecision + Send + Sync,
116{
117    fn intercept(
118        &self,
119        transport: Arc<Transport>,
120        session_id: SessionId,
121        event: RequestPausedEvent,
122    ) -> RequestPausedDecision {
123        self(transport, session_id, event)
124    }
125}
126
127type RequestIntercept = dyn RequestInterceptor + Send + Sync;
128
129pub trait EventListener<T> {
130    fn on_event(&self, event: &T);
131}
132
133impl<T, F: Fn(&T) + Send + Sync> EventListener<T> for F {
134    fn on_event(&self, event: &T) {
135        self(event);
136    }
137}
138
139pub trait Binding {
140    fn call_binding(&self, data: Json);
141}
142
143impl<T: Fn(Json) + Send + Sync> Binding for T {
144    fn call_binding(&self, data: Json) {
145        self(data);
146    }
147}
148
149pub type SafeBinding = dyn Binding + Send + Sync;
150
151pub type FunctionBinding = HashMap<String, Arc<SafeBinding>>;
152
153// type SyncSendEvent = dyn EventListener<Event> + Send + Sync;
154
155/// A handle to a single page. Exposes methods for simulating user actions (clicking,
156/// typing), and also for getting information about the DOM and other parts of the page.
157pub struct Tab {
158    target_id: TargetID,
159    transport: Arc<Transport>,
160    session_id: SessionId,
161    navigating: Arc<AtomicBool>,
162    target_info: Arc<Mutex<TargetInfo>>,
163    request_interceptor: Arc<Mutex<Arc<RequestIntercept>>>,
164    response_handler: Arc<Mutex<HashMap<String, ResponseHandler>>>,
165    loading_failed_handler: Arc<Mutex<HashMap<String, LoadingFailedHandler>>>,
166    auth_handler: Arc<Mutex<AuthChallengeResponse>>,
167    default_timeout: Arc<RwLock<Duration>>,
168    page_bindings: Arc<Mutex<FunctionBinding>>,
169    event_listeners: Arc<Mutex<Vec<Arc<SyncSendEvent>>>>,
170    slow_motion_multiplier: Arc<RwLock<f64>>, // there's no AtomicF64, otherwise would use that
171}
172
173#[derive(Debug, Error)]
174#[error("No element found")]
175pub struct NoElementFound {}
176
177#[derive(Debug, Error)]
178#[error("Navigate failed: {}", error_text)]
179pub struct NavigationFailed {
180    error_text: String,
181}
182
183#[derive(Debug, Error)]
184#[error("No LocalStorage item was found")]
185pub struct NoLocalStorageItemFound {}
186
187#[derive(Debug, Error)]
188#[error("No UserAgent evaluated")]
189pub struct NoUserAgentEvaluated {}
190
191impl NoElementFound {
192    pub fn map(error: Error) -> Error {
193        match error.downcast::<RemoteError>() {
194            Ok(remote_error) => {
195                match remote_error.message.as_ref() {
196                    // This error is expected and occurs while the page is still loading,
197                    // hence we shadow it and respond the element is not found
198                    "Could not find node with given id" => Self {}.into(),
199
200                    // Any other error is unexpected and should be reported
201                    _ => remote_error.into(),
202                }
203            }
204            // Return original error if downcasting to RemoteError fails
205            Err(original_error) => original_error,
206        }
207    }
208}
209
210impl Tab {
211    pub fn new(target_info: TargetInfo, transport: Arc<Transport>) -> Result<Self> {
212        let target_id = target_info.target_id.clone();
213
214        let session_id = transport
215            .call_method_on_browser(AttachToTarget {
216                target_id: target_id.clone(),
217                flatten: None,
218            })?
219            .session_id
220            .into();
221
222        debug!("New tab attached with session ID: {:?}", session_id);
223
224        let target_info_mutex = Arc::new(Mutex::new(target_info));
225
226        let tab = Self {
227            target_id,
228            transport,
229            session_id,
230            navigating: Arc::new(AtomicBool::new(false)),
231            target_info: target_info_mutex,
232            page_bindings: Arc::new(Mutex::new(HashMap::new())),
233            request_interceptor: Arc::new(Mutex::new(Arc::new(
234                |_transport, _session_id, _interception| RequestPausedDecision::Continue(None),
235            ))),
236            response_handler: Arc::new(Mutex::new(HashMap::new())),
237            loading_failed_handler: Arc::new(Mutex::new(HashMap::new())),
238            auth_handler: Arc::new(Mutex::new(AuthChallengeResponse {
239                response: Fetch::AuthChallengeResponseResponse::Default,
240                username: None,
241                password: None,
242            })),
243            default_timeout: Arc::new(RwLock::new(Duration::from_secs(20))),
244            event_listeners: Arc::new(Mutex::new(Vec::new())),
245            slow_motion_multiplier: Arc::new(RwLock::new(0.0)),
246        };
247
248        tab.call_method(Page::Enable(None))?;
249        tab.call_method(Page::SetLifecycleEventsEnabled { enabled: true })?;
250
251        tab.start_event_handler_thread();
252
253        Ok(tab)
254    }
255
256    pub fn update_target_info(&self, target_info: TargetInfo) {
257        let mut info = self.target_info.lock().unwrap();
258        *info = target_info;
259    }
260
261    pub fn get_target_id(&self) -> &TargetID {
262        &self.target_id
263    }
264
265    /// Fetches the most recent info about this target
266    pub fn get_target_info(&self) -> Result<TargetInfo> {
267        Ok(self
268            .call_method(Target::GetTargetInfo {
269                target_id: Some(self.get_target_id().to_string()),
270            })?
271            .target_info)
272    }
273
274    pub fn get_browser_context_id(&self) -> Result<Option<String>> {
275        Ok(self.get_target_info()?.browser_context_id)
276    }
277
278    pub fn get_url(&self) -> String {
279        let info = self.target_info.lock().unwrap();
280        info.url.clone()
281    }
282
283    /// Allows overriding user agent with the given string.
284    pub fn set_user_agent(
285        &self,
286        user_agent: &str,
287        accept_language: Option<&str>,
288        platform: Option<&str>,
289    ) -> Result<()> {
290        self.call_method(SetUserAgentOverride {
291            user_agent: user_agent.to_string(),
292            accept_language: accept_language.map(std::string::ToString::to_string),
293            platform: platform.map(std::string::ToString::to_string),
294            user_agent_metadata: None,
295        })
296        .map(|_| ())
297    }
298
299    fn start_event_handler_thread(&self) {
300        let transport: Arc<Transport> = Arc::clone(&self.transport);
301        let incoming_events_rx = self
302            .transport
303            .listen_to_target_events(self.session_id.clone());
304        let navigating = Arc::clone(&self.navigating);
305        let interceptor_mutex = Arc::clone(&self.request_interceptor);
306        let response_handler_mutex = self.response_handler.clone();
307        let loading_failed_handler_mutex = self.loading_failed_handler.clone();
308        let auth_handler_mutex = self.auth_handler.clone();
309        let session_id = self.session_id.clone();
310        let listeners_mutex = Arc::clone(&self.event_listeners);
311
312        let bindings_mutex = Arc::clone(&self.page_bindings);
313        let received_event_params = Arc::new(Mutex::new(HashMap::new()));
314
315        thread::spawn(move || {
316            for event in incoming_events_rx {
317                let listeners = listeners_mutex.lock().unwrap();
318                listeners.iter().for_each(|listener| {
319                    listener.on_event(&event);
320                });
321
322                match event {
323                    Event::PageLifecycleEvent(lifecycle_event) => {
324                        let event_name = lifecycle_event.params.name.as_ref();
325                        trace!("Lifecycle event: {}", event_name);
326                        match event_name {
327                            "networkAlmostIdle" => {
328                                navigating.store(false, Ordering::SeqCst);
329                            }
330                            "init" => {
331                                navigating.store(true, Ordering::SeqCst);
332                            }
333                            _ => {}
334                        }
335                    }
336                    Event::RuntimeBindingCalled(binding) => {
337                        let bindings = bindings_mutex.lock().unwrap().clone();
338
339                        let name = binding.params.name;
340                        let payload = binding.params.payload;
341
342                        let func = &Arc::clone(bindings.get(&name).unwrap());
343
344                        func.call_binding(json!(payload));
345                    }
346                    Event::FetchRequestPaused(event) => {
347                        let interceptor = interceptor_mutex.lock().unwrap();
348                        let decision = interceptor.intercept(
349                            Arc::clone(&transport),
350                            session_id.clone(),
351                            event.clone(),
352                        );
353                        let result = match decision {
354                            RequestPausedDecision::Continue(continue_request) => {
355                                if let Some(continue_request) = continue_request {
356                                    transport
357                                        .call_method_on_target(session_id.clone(), continue_request)
358                                        .map(|_| ())
359                                } else {
360                                    transport
361                                        .call_method_on_target(
362                                            session_id.clone(),
363                                            ContinueRequest {
364                                                request_id: event.params.request_id,
365                                                url: None,
366                                                method: None,
367                                                post_data: None,
368                                                headers: None,
369                                                intercept_response: None,
370                                            },
371                                        )
372                                        .map(|_| ())
373                                }
374                            }
375                            RequestPausedDecision::Fulfill(fulfill_request) => transport
376                                .call_method_on_target(session_id.clone(), fulfill_request)
377                                .map(|_| ()),
378                            RequestPausedDecision::Fail(fail_request) => transport
379                                .call_method_on_target(session_id.clone(), fail_request)
380                                .map(|_| ()),
381                        };
382                        if result.is_err() {
383                            warn!("Tried to handle request after connection was closed");
384                        }
385                    }
386                    Event::FetchAuthRequired(event) => {
387                        let auth_challenge_response = auth_handler_mutex.lock().unwrap().clone();
388
389                        let request_id = event.params.request_id;
390                        let method = ContinueWithAuth {
391                            request_id,
392                            auth_challenge_response,
393                        };
394                        let result = transport.call_method_on_target(session_id.clone(), method);
395                        if result.is_err() {
396                            warn!("Tried to handle request after connection was closed");
397                        }
398                    }
399                    Event::NetworkResponseReceived(ev) => {
400                        let request_id = ev.params.request_id.clone();
401                        received_event_params
402                            .lock()
403                            .unwrap()
404                            .insert(request_id, ev.params);
405                    }
406                    Event::NetworkLoadingFinished(ev) => {
407                        response_handler_mutex.lock().unwrap().iter().for_each(
408                            |(_name, handler)| {
409                                let request_id = ev.params.request_id.clone();
410                                let retrieve_body = || {
411                                    let method = GetResponseBody {
412                                        request_id: request_id.clone(),
413                                    };
414                                    transport.call_method_on_target(session_id.clone(), method)
415                                };
416                                if let Some(params) =
417                                    received_event_params.lock().unwrap().get(&request_id)
418                                {
419                                    handler(params.clone(), &retrieve_body);
420                                } else {
421                                    warn!("Request id does not exist");
422                                }
423                            },
424                        );
425                    }
426                    Event::NetworkLoadingFailed(ev) => loading_failed_handler_mutex
427                        .lock()
428                        .unwrap()
429                        .iter()
430                        .for_each(|(_name, handler)| {
431                            let request_id = ev.params.request_id.clone();
432
433                            if let Some(params) =
434                                received_event_params.lock().unwrap().get(&request_id)
435                            {
436                                handler(params.clone(), ev.params.clone());
437                            } else {
438                                warn!("Request id does not exist");
439                            }
440                        }),
441                    _ => {
442                        let raw_event = format!("{event:?}");
443                        trace!(
444                            "Unhandled event: {}",
445                            raw_event.chars().take(50).collect::<String>()
446                        );
447                    }
448                }
449            }
450            info!("finished tab's event handling loop");
451        });
452    }
453
454    pub fn expose_function(&self, name: &str, func: Arc<SafeBinding>) -> Result<()> {
455        let bindings_mutex = Arc::clone(&self.page_bindings);
456
457        let mut bindings = bindings_mutex.lock().unwrap();
458
459        bindings.insert(name.to_string(), func);
460
461        let expression = r"
462        (function addPageBinding(bindingName) {
463            const binding = window[bindingName];
464            window[bindingName] = (...args) => {
465              const me = window[bindingName];
466              let callbacks = me['callbacks'];
467              if (!callbacks) {
468                callbacks = new Map();
469                me['callbacks'] = callbacks;
470              }
471              const seq = (me['lastSeq'] || 0) + 1;
472              me['lastSeq'] = seq;
473              const promise = new Promise((resolve, reject) => callbacks.set(seq, {resolve, reject}));
474              binding(JSON.stringify({name: bindingName, seq, args}));
475              return promise;
476            };
477          })()
478        "; // https://github.com/puppeteer/puppeteer/blob/97c9fe2520723d45a5a86da06b888ae888d400be/src/common/helper.ts#L183
479
480        self.call_method(AddBinding {
481            name: name.to_string(),
482            execution_context_id: None,
483            execution_context_name: None,
484        })?;
485
486        self.call_method(AddScriptToEvaluateOnNewDocument {
487            source: expression.to_string(),
488            world_name: None,
489            include_command_line_api: None,
490            run_immediately: None,
491        })?;
492
493        Ok(())
494    }
495
496    pub fn remove_function(&self, name: &str) -> Result<()> {
497        let bindings_mutex = Arc::clone(&self.page_bindings);
498
499        let mut bindings = bindings_mutex.lock().unwrap();
500
501        bindings.remove(name).unwrap();
502
503        Ok(())
504    }
505
506    pub fn call_method<C>(&self, method: C) -> Result<C::ReturnObject>
507    where
508        C: Method + serde::Serialize + std::fmt::Debug,
509    {
510        trace!("Calling method: {:?}", method);
511        let result = self
512            .transport
513            .call_method_on_target(self.session_id.clone(), method);
514        let result_string = format!("{result:?}");
515        trace!(
516            "Got result: {:?}",
517            result_string.chars().take(70).collect::<String>()
518        );
519        result
520    }
521
522    pub fn wait_until_navigated(&self) -> Result<&Self> {
523        let navigating = Arc::clone(&self.navigating);
524        let timeout = *self.default_timeout.read().unwrap();
525
526        util::Wait::with_timeout(timeout).until(|| {
527            if navigating.load(Ordering::SeqCst) {
528                None
529            } else {
530                Some(true)
531            }
532        })?;
533        debug!("A tab finished navigating");
534
535        Ok(self)
536    }
537
538    // Pulls focus to this tab
539    pub fn bring_to_front(&self) -> Result<Page::BringToFrontReturnObject> {
540        self.call_method(Page::BringToFront(None))
541    }
542
543    pub fn navigate_to(&self, url: &str) -> Result<&Self> {
544        let return_object = self.call_method(Navigate {
545            url: url.to_string(),
546            referrer: None,
547            transition_Type: None,
548            frame_id: None,
549            referrer_policy: None,
550        })?;
551        if let Some(error_text) = return_object.error_text {
552            return Err(NavigationFailed { error_text }.into());
553        }
554
555        let navigating = Arc::clone(&self.navigating);
556        navigating.store(true, Ordering::SeqCst);
557
558        info!("Navigating a tab to {}", url);
559
560        Ok(self)
561    }
562
563    /// Set default timeout for the tab
564    ///
565    /// This will be applied to all [wait_for_element](Tab::wait_for_element) and [wait_for_elements](Tab::wait_for_elements) calls for this tab
566    ///
567    /// ```rust
568    /// # use anyhow::Result;
569    /// # fn main() -> Result<()> {
570    /// # use headless_chrome::Browser;
571    /// # let browser = Browser::default()?;
572    /// let tab = browser.new_tab()?;
573    /// tab.set_default_timeout(std::time::Duration::from_secs(5));
574    /// #
575    /// # Ok(())
576    /// # }
577    /// ```
578    pub fn set_default_timeout(&self, timeout: Duration) -> &Self {
579        let mut current_timeout = self.default_timeout.write().unwrap();
580        *current_timeout = timeout;
581        self
582    }
583
584    /// Analogous to Puppeteer's ['slowMo' option](https://github.com/GoogleChrome/puppeteer/blob/v1.20.0/docs/api.md#puppeteerconnectoptions),
585    /// but with some differences:
586    ///
587    /// * It doesn't add a delay after literally every message sent via the protocol, but instead
588    ///   just for:
589    ///     * clicking a specific point on the page (default: 100ms before moving the mouse, 250ms
590    ///       before pressing and releasting mouse button)
591    ///     * pressing a key (default: 25 ms)
592    ///     * reloading the page (default: 100ms)
593    ///     * closing a tab (default: 100ms)
594    /// * Instead of an absolute number of milliseconds, it's a multiplier, so that we can delay
595    ///   longer on certain actions like clicking or moving the mouse, and shorter on others like
596    ///   on pressing a key (or the individual 'mouseDown' and 'mouseUp' actions that go across the
597    ///   wire. If the delay was always the same, filling out a form (e.g.) would take ages).
598    ///
599    /// By default the multiplier is set to zero, which effectively disables the slow motion.
600    ///
601    /// The defaults for the various actions (i.e. how long we sleep for when
602    /// multiplier is 1.0) are supposed to be just slow enough to help a human see what's going on
603    /// as a test runs.
604    pub fn set_slow_motion_multiplier(&self, multiplier: f64) -> &Self {
605        let mut slow_motion_multiplier = self.slow_motion_multiplier.write().unwrap();
606        *slow_motion_multiplier = multiplier;
607        self
608    }
609
610    fn optional_slow_motion_sleep(&self, millis: u64) {
611        let multiplier = self.slow_motion_multiplier.read().unwrap();
612        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
613        let scaled_millis = millis * *multiplier as u64;
614        sleep(Duration::from_millis(scaled_millis));
615    }
616
617    pub fn wait_for_element(&self, selector: &str) -> Result<Element<'_>> {
618        self.wait_for_element_with_custom_timeout(selector, *self.default_timeout.read().unwrap())
619    }
620
621    pub fn wait_for_xpath(&self, selector: &str) -> Result<Element<'_>> {
622        self.wait_for_xpath_with_custom_timeout(selector, *self.default_timeout.read().unwrap())
623    }
624
625    pub fn wait_for_element_with_custom_timeout(
626        &self,
627        selector: &str,
628        timeout: std::time::Duration,
629    ) -> Result<Element<'_>> {
630        debug!("Waiting for element with selector: {:?}", selector);
631        util::Wait::with_timeout(timeout).strict_until(
632            || self.find_element(selector),
633            Error::downcast::<NoElementFound>,
634        )
635    }
636
637    pub fn wait_for_xpath_with_custom_timeout(
638        &self,
639        selector: &str,
640        timeout: std::time::Duration,
641    ) -> Result<Element<'_>> {
642        debug!("Waiting for element with selector: {:?}", selector);
643        util::Wait::with_timeout(timeout).strict_until(
644            || self.find_element_by_xpath(selector),
645            Error::downcast::<NoElementFound>,
646        )
647    }
648
649    pub fn wait_for_elements(&self, selector: &str) -> Result<Vec<Element<'_>>> {
650        debug!("Waiting for element with selector: {:?}", selector);
651        util::Wait::with_timeout(*self.default_timeout.read().unwrap()).strict_until(
652            || self.find_elements(selector),
653            Error::downcast::<NoElementFound>,
654        )
655    }
656
657    pub fn wait_for_elements_by_xpath(&self, selector: &str) -> Result<Vec<Element<'_>>> {
658        debug!("Waiting for element with selector: {:?}", selector);
659        util::Wait::with_timeout(*self.default_timeout.read().unwrap()).strict_until(
660            || self.find_elements_by_xpath(selector),
661            Error::downcast::<NoElementFound>,
662        )
663    }
664
665    /// Returns the first element in the document which matches the given selector.
666    ///
667    /// Equivalent to the following JS:
668    ///
669    /// ```js
670    /// document.querySelector(selector)
671    /// ```
672    ///
673    /// ```rust
674    /// # use anyhow::Result;
675    /// # // Awful hack to get access to testing utils common between integration, doctest, and unit tests
676    /// # mod server {
677    /// #     include!("../../testing_utils/server.rs");
678    /// # }
679    /// # fn main() -> Result<()> {
680    /// #
681    /// use headless_chrome::Browser;
682    ///
683    /// let browser = Browser::default()?;
684    /// let initial_tab = browser.new_tab()?;
685    ///
686    /// let file_server = server::Server::with_dumb_html(include_str!("../../../tests/simple.html"));
687    /// let element = initial_tab.navigate_to(&file_server.url())?
688    ///     .wait_until_navigated()?
689    ///     .find_element("div#foobar")?;
690    /// let attrs = element.get_attributes()?.unwrap();
691    /// assert_eq!(attrs["id"], "foobar");
692    /// #
693    /// # Ok(())
694    /// # }
695    /// ```
696    pub fn find_element(&self, selector: &str) -> Result<Element<'_>> {
697        let root_node_id = self.get_document()?.node_id;
698        trace!("Looking up element via selector: {}", selector);
699
700        self.run_query_selector_on_node(root_node_id, selector)
701    }
702
703    pub fn find_element_by_xpath(&self, query: &str) -> Result<Element<'_>> {
704        self.get_document()?;
705
706        self.call_method(DOM::PerformSearch {
707            query: query.to_string(),
708            include_user_agent_shadow_dom: None,
709        })
710        .map(|o| {
711            match self.call_method(DOM::GetSearchResults {
712                search_id: o.search_id,
713                from_index: 0,
714                to_index: o.result_count,
715            }) {
716                Ok(res) => res.node_ids.first().copied().unwrap_or(0),
717                Err(_) => 0,
718            }
719        })
720        .and_then(|id| {
721            if id == 0 {
722                Err(NoElementFound {}.into())
723            } else {
724                Ok(Element::new(self, id)?)
725            }
726        })
727    }
728
729    pub fn run_query_selector_on_node(
730        &self,
731        node_id: NodeId,
732        selector: &str,
733    ) -> Result<Element<'_>> {
734        let node_id = self
735            .call_method(DOM::QuerySelector {
736                node_id,
737                selector: selector.to_string(),
738            })
739            .map_err(NoElementFound::map)?
740            .node_id;
741
742        Element::new(self, node_id)
743    }
744
745    pub fn run_query_selector_all_on_node(
746        &self,
747        node_id: NodeId,
748        selector: &str,
749    ) -> Result<Vec<Element<'_>>> {
750        let node_ids = self
751            .call_method(DOM::QuerySelectorAll {
752                node_id,
753                selector: selector.to_string(),
754            })
755            .map_err(NoElementFound::map)?
756            .node_ids;
757
758        node_ids
759            .iter()
760            .map(|node_id| Element::new(self, *node_id))
761            .collect()
762    }
763
764    pub fn get_document(&self) -> Result<Node> {
765        Ok(self
766            .call_method(DOM::GetDocument {
767                depth: Some(0),
768                pierce: Some(false),
769            })?
770            .root)
771    }
772
773    /// Get the full HTML contents of the page.
774    pub fn get_content(&self) -> Result<String> {
775        let func = "
776            (function () {
777                let retVal = '';
778                if (document.doctype)
779                    retVal = new XMLSerializer().serializeToString(document.doctype);
780                if (document.documentElement)
781                    retVal += document.documentElement.outerHTML;
782                return retVal;
783            })();";
784        let html = self.evaluate(func, false)?.value.unwrap();
785        Ok(String::from(html.as_str().unwrap()))
786    }
787
788    pub fn find_elements(&self, selector: &str) -> Result<Vec<Element<'_>>> {
789        trace!("Looking up elements via selector: {}", selector);
790
791        let root_node_id = self.get_document()?.node_id;
792        let node_ids = self
793            .call_method(DOM::QuerySelectorAll {
794                node_id: root_node_id,
795                selector: selector.to_string(),
796            })
797            .map_err(NoElementFound::map)?
798            .node_ids;
799
800        if node_ids.is_empty() {
801            return Err(NoElementFound {}.into());
802        }
803
804        node_ids
805            .into_iter()
806            .map(|node_id| Element::new(self, node_id))
807            .collect()
808    }
809
810    pub fn find_elements_by_xpath(&self, query: &str) -> Result<Vec<Element<'_>>> {
811        self.get_document()?;
812
813        self.call_method(DOM::PerformSearch {
814            query: query.to_string(),
815            include_user_agent_shadow_dom: None,
816        })
817        .and_then(|o| {
818            Ok(self
819                .call_method(DOM::GetSearchResults {
820                    search_id: o.search_id,
821                    from_index: 0,
822                    to_index: o.result_count,
823                })?
824                .node_ids)
825        })
826        .and_then(|ids| {
827            ids.iter()
828                .filter(|id| **id != 0)
829                .map(|id| Element::new(self, *id))
830                .collect()
831        })
832    }
833
834    pub fn describe_node(&self, node_id: NodeId) -> Result<Node> {
835        let node = self
836            .call_method(DOM::DescribeNode {
837                node_id: Some(node_id),
838                backend_node_id: None,
839                depth: Some(100),
840                object_id: None,
841                pierce: None,
842            })?
843            .node;
844        Ok(node)
845    }
846
847    pub fn type_str(&self, string_to_type: &str) -> Result<&Self> {
848        for c in string_to_type.split("") {
849            // split call above will have empty string at start and end which we won't type
850            if c.is_empty() {
851                continue;
852            }
853            let definition = keys::get_key_definition(c);
854            // https://github.com/puppeteer/puppeteer/blob/b8806d5625ca7835abbaf2e997b0bf35a5679e29/src/common/Input.ts#L239-L245
855            match definition {
856                Ok(key) => {
857                    let v: DispatchKeyEvent = key.into();
858
859                    self.call_method(v.clone())?;
860                    self.call_method(DispatchKeyEvent {
861                        Type: Input::DispatchKeyEventTypeOption::KeyUp,
862                        ..v
863                    })?;
864                }
865                Err(_) => {
866                    self.send_character(c)?;
867                }
868            }
869        }
870        Ok(self)
871    }
872
873    /// Does the same as `type_str` but it only dispatches a `keypress` and `input` event.
874    /// It does not send a `keydown` or `keyup` event.
875    ///
876    /// What this means is that it is much faster.
877    /// It is especially useful when you have a lot of text as input.
878    pub fn send_character(&self, char_to_send: &str) -> Result<&Self> {
879        self.call_method(Input::InsertText {
880            text: char_to_send.to_string(),
881        })?;
882        Ok(self)
883    }
884
885    /// Press a key on the keyboard, optionally with some modifier keys.
886    /// See [this file](https://github.com/puppeteer/puppeteer/blob/62da2366c65b335751896afbb0206f23c61436f1/lib/USKeyboardLayout.js)
887    /// for a full definition of which strings correspond with which
888    /// keys.
889    pub fn press_key_with_modifiers(
890        &self,
891        key: &str,
892        modifiers: Option<&[ModifierKey]>,
893    ) -> Result<&Self> {
894        // See https://github.com/GoogleChrome/puppeteer/blob/62da2366c65b335751896afbb0206f23c61436f1/lib/Input.js#L114-L115
895        let definition = keys::get_key_definition(key)?;
896
897        let text = definition
898            .text
899            .or({
900                if definition.key.len() == 1 {
901                    Some(definition.key)
902                } else {
903                    None
904                }
905            })
906            .map(std::string::ToString::to_string);
907
908        // See https://github.com/GoogleChrome/puppeteer/blob/62da2366c65b335751896afbb0206f23c61436f1/lib/Input.js#L52
909        let key_down_event_type = if text.is_some() {
910            Input::DispatchKeyEventTypeOption::KeyDown
911        } else {
912            Input::DispatchKeyEventTypeOption::RawKeyDown
913        };
914
915        let key = Some(definition.key.to_string());
916        let code = Some(definition.code.to_string());
917
918        let modifiers = modifiers.map(|v| v.iter().fold(0, |acc, e| acc | *e as u32));
919
920        self.optional_slow_motion_sleep(25);
921
922        self.call_method(Input::DispatchKeyEvent {
923            Type: key_down_event_type,
924            key: key.clone(),
925            text: text.clone(),
926            code: code.clone(),
927            windows_virtual_key_code: Some(definition.key_code),
928            native_virtual_key_code: Some(definition.key_code),
929            modifiers,
930            timestamp: None,
931            unmodified_text: None,
932            key_identifier: None,
933            auto_repeat: None,
934            is_keypad: None,
935            is_system_key: None,
936            location: None,
937            commands: None,
938        })?;
939        self.call_method(Input::DispatchKeyEvent {
940            Type: Input::DispatchKeyEventTypeOption::KeyUp,
941            key,
942            text,
943            code,
944            windows_virtual_key_code: Some(definition.key_code),
945            native_virtual_key_code: Some(definition.key_code),
946            modifiers,
947            timestamp: None,
948            unmodified_text: None,
949            key_identifier: None,
950            auto_repeat: None,
951            is_keypad: None,
952            is_system_key: None,
953            location: None,
954            commands: None,
955        })?;
956        Ok(self)
957    }
958
959    /// Press a key on the keyboard. See [this file](https://github.com/puppeteer/puppeteer/blob/62da2366c65b335751896afbb0206f23c61436f1/lib/USKeyboardLayout.js)
960    /// for a full definition of which strings correspond with which
961    /// keys.
962    pub fn press_key(&self, key: &str) -> Result<&Self> {
963        self.press_key_with_modifiers(key, None)
964    }
965
966    /// Moves the mouse to this point (dispatches a mouseMoved event)
967    pub fn move_mouse_to_point(&self, point: Point) -> Result<&Self> {
968        if point.x == 0.0 && point.y == 0.0 {
969            warn!("Midpoint of element shouldn't be 0,0. Something is probably wrong.");
970        }
971
972        self.optional_slow_motion_sleep(100);
973
974        self.call_method(Input::DispatchMouseEvent {
975            Type: Input::DispatchMouseEventTypeOption::MouseMoved,
976            x: point.x,
977            y: point.y,
978            modifiers: None,
979            timestamp: None,
980            button: None,
981            buttons: None,
982            click_count: None,
983            force: None,
984            tangential_pressure: None,
985            tilt_x: None,
986            tilt_y: None,
987            twist: None,
988            delta_x: None,
989            delta_y: None,
990            pointer_Type: None,
991        })?;
992
993        Ok(self)
994    }
995
996    pub fn click_point(&self, point: Point) -> Result<&Self> {
997        trace!("Clicking point: {:?}", point);
998        if point.x == 0.0 && point.y == 0.0 {
999            warn!("Midpoint of element shouldn't be 0,0. Something is probably wrong.");
1000        }
1001
1002        self.move_mouse_to_point(point)?;
1003
1004        self.optional_slow_motion_sleep(250);
1005        self.call_method(Input::DispatchMouseEvent {
1006            Type: Input::DispatchMouseEventTypeOption::MousePressed,
1007            x: point.x,
1008            y: point.y,
1009            button: Some(Input::MouseButton::Left),
1010            click_count: Some(1),
1011            modifiers: None,
1012            timestamp: None,
1013            buttons: None,
1014            force: None,
1015            tangential_pressure: None,
1016            tilt_x: None,
1017            tilt_y: None,
1018            twist: None,
1019            delta_x: None,
1020            delta_y: None,
1021            pointer_Type: None,
1022        })?;
1023        self.call_method(Input::DispatchMouseEvent {
1024            Type: Input::DispatchMouseEventTypeOption::MouseReleased,
1025            x: point.x,
1026            y: point.y,
1027            button: Some(Input::MouseButton::Left),
1028            click_count: Some(1),
1029            modifiers: None,
1030            timestamp: None,
1031            buttons: None,
1032            force: None,
1033            tangential_pressure: None,
1034            tilt_x: None,
1035            tilt_y: None,
1036            twist: None,
1037            delta_x: None,
1038            delta_y: None,
1039            pointer_Type: None,
1040        })?;
1041        Ok(self)
1042    }
1043
1044    /// Capture a screenshot of the current page.
1045    ///
1046    /// If `clip` is given, the screenshot is taken of the specified region only.
1047    /// `Element::get_box_model()` can be used to get regions of certain elements
1048    /// on the page; there is also `Element::capture_screenshot()` as a shorthand.
1049    ///
1050    /// If `from_surface` is true, the screenshot is taken from the surface rather than
1051    /// the view.
1052    ///
1053    /// ```rust,no_run
1054    /// # use anyhow::Result;
1055    /// # fn main() -> Result<()> {
1056    /// #
1057    /// use headless_chrome::{protocol::page::ScreenshotFormat, Browser, LaunchOptions};
1058    /// let browser = Browser::new(LaunchOptions::default_builder().build().unwrap())?;
1059    /// let tab = browser.new_tab()?;
1060    /// let viewport = tab.navigate_to("https://en.wikipedia.org/wiki/WebKit")?
1061    ///     .wait_for_element("#mw-content-text > div > table.infobox.vevent")?
1062    ///     .get_box_model()?
1063    ///     .margin_viewport();
1064    ///  let png_data = tab.capture_screenshot(ScreenshotFormat::PNG, Some(viewport), true)?;
1065    /// #
1066    /// # Ok(())
1067    /// # }
1068    /// ```
1069    pub fn capture_screenshot(
1070        &self,
1071        format: Page::CaptureScreenshotFormatOption,
1072        quality: Option<u32>,
1073        clip: Option<Page::Viewport>,
1074        from_surface: bool,
1075    ) -> Result<Vec<u8>> {
1076        let data = self
1077            .call_method(Page::CaptureScreenshot {
1078                format: Some(format),
1079                clip,
1080                quality,
1081                from_surface: Some(from_surface),
1082                capture_beyond_viewport: None,
1083                optimize_for_speed: None,
1084            })?
1085            .data;
1086        base64::prelude::BASE64_STANDARD
1087            .decode(data)
1088            .map_err(Into::into)
1089    }
1090
1091    pub fn print_to_pdf(&self, options: Option<PrintToPdfOptions>) -> Result<Vec<u8>> {
1092        if let Some(options) = options {
1093            let transfer_mode: Option<Page::PrintToPDFTransfer_modeOption> =
1094                options.transfer_mode.and_then(std::convert::Into::into);
1095            let data = self
1096                .call_method(Page::PrintToPDF {
1097                    landscape: options.landscape,
1098                    display_header_footer: options.display_header_footer,
1099                    print_background: options.print_background,
1100                    scale: options.scale,
1101                    paper_width: options.paper_width,
1102                    paper_height: options.paper_height,
1103                    margin_top: options.margin_top,
1104                    margin_bottom: options.margin_bottom,
1105                    margin_left: options.margin_left,
1106                    margin_right: options.margin_right,
1107                    page_ranges: options.page_ranges,
1108                    generate_tagged_pdf: options.generate_tagged_pdf,
1109                    generate_document_outline: options.generate_document_outline,
1110                    header_template: options.header_template,
1111                    footer_template: options.footer_template,
1112                    prefer_css_page_size: options.prefer_css_page_size,
1113                    transfer_mode,
1114                })?
1115                .data;
1116            base64::prelude::BASE64_STANDARD
1117                .decode(data)
1118                .map_err(Into::into)
1119        } else {
1120            let data = self
1121                .call_method(Page::PrintToPDF {
1122                    ..Default::default()
1123                })?
1124                .data;
1125
1126            base64::prelude::BASE64_STANDARD
1127                .decode(data)
1128                .map_err(Into::into)
1129        }
1130    }
1131
1132    /// Reloads given page optionally ignoring the cache
1133    ///
1134    /// If `ignore_cache` is true, the browser cache is ignored (as if the user pressed Shift+F5).
1135    /// If `script_to_evaluate` is given, the script will be injected into all frames of the
1136    /// inspected page after reload. Argument will be ignored if reloading dataURL origin.
1137    pub fn reload(
1138        &self,
1139        ignore_cache: bool,
1140        script_to_evaluate_on_load: Option<&str>,
1141    ) -> Result<&Self> {
1142        self.optional_slow_motion_sleep(100);
1143        self.call_method(Page::Reload {
1144            loader_id: None,
1145            ignore_cache: Some(ignore_cache),
1146            script_to_evaluate_on_load: script_to_evaluate_on_load
1147                .map(std::string::ToString::to_string),
1148        })?;
1149        Ok(self)
1150    }
1151
1152    /// Set the background color of the dom to transparent.
1153    ///
1154    /// Useful when you want capture a .png
1155    ///
1156    /// ```rust,no_run
1157    /// # use anyhow::Result;
1158    /// # fn main() -> Result<()> {
1159    /// #
1160    /// use headless_chrome::{protocol::page::ScreenshotFormat, Browser, LaunchOptions};
1161    /// let browser = Browser::new(LaunchOptions::default_builder().build().unwrap())?;
1162    /// let tab = browser.new_tab()?;
1163    /// tab.set_transparent_background_color()?;
1164    ///
1165    /// #
1166    /// # Ok(())
1167    /// # }
1168    /// ```
1169    pub fn set_transparent_background_color(&self) -> Result<&Self> {
1170        self.call_method(Emulation::SetDefaultBackgroundColorOverride {
1171            color: Some(DOM::RGBA {
1172                r: 0,
1173                g: 0,
1174                b: 0,
1175                a: Some(0.0),
1176            }),
1177        })?;
1178        Ok(self)
1179    }
1180
1181    /// Set the default background color of the dom.
1182    ///
1183    /// Pass a RGBA to override the background color of the dom.
1184    ///
1185    /// ```rust,no_run
1186    /// # use anyhow::Result;
1187    /// # fn main() -> Result<()> {
1188    /// #
1189    /// use headless_chrome::{protocol::page::ScreenshotFormat, Browser, LaunchOptions};
1190    /// let browser = Browser::new(LaunchOptions::default_builder().build().unwrap())?;
1191    /// let tab = browser.new_tab()?;
1192    /// tab.set_background_color( color: RGBA { r: 255, g: 0, b: 0, a: 1.,})?;
1193    ///
1194    /// #
1195    /// # Ok(())
1196    /// # }
1197    /// ```
1198    pub fn set_background_color(&self, color: DOM::RGBA) -> Result<&Self> {
1199        self.call_method(Emulation::SetDefaultBackgroundColorOverride { color: Some(color) })?;
1200        Ok(self)
1201    }
1202
1203    /// Enables the profiler
1204    pub fn enable_profiler(&self) -> Result<&Self> {
1205        self.call_method(Profiler::Enable(None))?;
1206
1207        Ok(self)
1208    }
1209
1210    /// Disables the profiler
1211    pub fn disable_profiler(&self) -> Result<&Self> {
1212        self.call_method(Profiler::Disable(None))?;
1213
1214        Ok(self)
1215    }
1216
1217    /// Starts tracking which lines of JS have been executed
1218    ///
1219    /// Will return error unless `enable_profiler` has been called.
1220    ///
1221    /// Equivalent to hitting the record button in the "coverage" tab in Chrome DevTools.
1222    /// See the file `tests/coverage.rs` for an example.
1223    ///
1224    /// By default we enable the 'detailed' flag on StartPreciseCoverage, which enables block-level
1225    /// granularity, and also enable 'call_count' (which when disabled always sets count to 1 or 0).
1226    ///
1227    pub fn start_js_coverage(&self) -> Result<&Self> {
1228        self.call_method(Profiler::StartPreciseCoverage {
1229            call_count: Some(true),
1230            detailed: Some(true),
1231            allow_triggered_updates: None,
1232        })?;
1233        Ok(self)
1234    }
1235
1236    /// Stops tracking which lines of JS have been executed
1237    /// If you're finished with the profiler, don't forget to call `disable_profiler`.
1238    pub fn stop_js_coverage(&self) -> Result<&Self> {
1239        self.call_method(Profiler::StopPreciseCoverage(None))?;
1240        Ok(self)
1241    }
1242
1243    /// Collect coverage data for the current isolate, and resets execution counters.
1244    ///
1245    /// Precise code coverage needs to have started (see `start_js_coverage`).
1246    ///
1247    /// Will only send information about code that's been executed since this method was last
1248    /// called, or (if this is the first time) since calling `start_js_coverage`.
1249    /// Another way of thinking about it is: every time you call this, the call counts for
1250    /// FunctionRanges are reset after returning.
1251    ///
1252    /// The format of the data is a little unintuitive, see here for details:
1253    /// <https://chromedevtools.github.io/devtools-protocol/tot/Profiler#type-ScriptCoverage>
1254    pub fn take_precise_js_coverage(&self) -> Result<Vec<Profiler::ScriptCoverage>> {
1255        let script_coverages = self
1256            .call_method(Profiler::TakePreciseCoverage(None))?
1257            .result;
1258        Ok(script_coverages)
1259    }
1260
1261    /// Enables fetch domain.
1262    pub fn enable_fetch(
1263        &self,
1264        patterns: Option<&[Fetch::RequestPattern]>,
1265        handle_auth_requests: Option<bool>,
1266    ) -> Result<&Self> {
1267        self.call_method(Fetch::Enable {
1268            patterns: patterns.map(Vec::from),
1269            handle_auth_requests,
1270        })?;
1271        Ok(self)
1272    }
1273
1274    /// Disables fetch domain
1275    pub fn disable_fetch(&self) -> Result<&Self> {
1276        self.call_method(Fetch::Disable(None))?;
1277        Ok(self)
1278    }
1279
1280    /// Allows you to inspect outgoing network requests from the tab, and optionally return
1281    /// your own responses to them
1282    ///
1283    /// The `interceptor` argument is a closure which takes this tab's `Transport` and its SessionID
1284    /// so that you can call methods from within the closure using `transport.call_method_on_target`.
1285    ///
1286    /// The closure needs to return a variant of `RequestPausedDecision`.
1287    pub fn enable_request_interception(&self, interceptor: Arc<RequestIntercept>) -> Result<()> {
1288        let mut current_interceptor = self.request_interceptor.lock().unwrap();
1289        *current_interceptor = interceptor;
1290        Ok(())
1291    }
1292
1293    pub fn authenticate(
1294        &self,
1295        username: Option<String>,
1296        password: Option<String>,
1297    ) -> Result<&Self> {
1298        let mut current_auth_handler = self.auth_handler.lock().unwrap();
1299        *current_auth_handler = AuthChallengeResponse {
1300            response: Fetch::AuthChallengeResponseResponse::ProvideCredentials,
1301            username,
1302            password,
1303        };
1304        Ok(self)
1305    }
1306
1307    /// Lets you register a listener for responses, and gives you a way to get the response body too.
1308    ///
1309    /// Please note that the 'response' does not include the *body* of the response -- Chrome tells
1310    /// us about them separately (because you might quickly get the status code and headers from a
1311    /// server well before you receive the entire response body which could, after all, be gigabytes
1312    /// long).
1313    ///
1314    /// Currently we leave it up to the caller to decide when to call `fetch_body` (the second
1315    /// argument to the response handler), although ideally it wouldn't be possible until Chrome has
1316    /// sent the `Network.loadingFinished` event.
1317    ///
1318    /// Return a option for ResponseHandler for existing handler with same name if existed.
1319    pub fn register_response_handling<S: ToString>(
1320        &self,
1321        handler_name: S,
1322        handler: ResponseHandler,
1323    ) -> Result<Option<ResponseHandler>> {
1324        self.call_method(Network::Enable {
1325            max_total_buffer_size: None,
1326            max_resource_buffer_size: None,
1327            max_post_data_size: None,
1328        })?;
1329        Ok(self
1330            .response_handler
1331            .lock()
1332            .unwrap()
1333            .insert(handler_name.to_string(), handler))
1334    }
1335
1336    pub fn register_loading_failed_handling<S: ToString>(
1337        &self,
1338        handler_name: S,
1339        handler: LoadingFailedHandler,
1340    ) -> Result<Option<LoadingFailedHandler>> {
1341        self.call_method(Network::Enable {
1342            max_total_buffer_size: None,
1343            max_resource_buffer_size: None,
1344            max_post_data_size: None,
1345        })?;
1346        Ok(self
1347            .loading_failed_handler
1348            .lock()
1349            .unwrap()
1350            .insert(handler_name.to_string(), handler))
1351    }
1352
1353    /// Deregister a response handler based on its name.
1354    ///
1355    /// Return a option for ResponseHandler for removed handler if existed.
1356    pub fn deregister_response_handling(
1357        &self,
1358        handler_name: &str,
1359    ) -> Result<Option<ResponseHandler>> {
1360        Ok(self.response_handler.lock().unwrap().remove(handler_name))
1361    }
1362
1363    /// Deregister all registered handlers.
1364    pub fn deregister_response_handling_all(&self) -> Result<()> {
1365        self.response_handler.lock().unwrap().clear();
1366        Ok(())
1367    }
1368
1369    /// Enables runtime domain.
1370    pub fn enable_runtime(&self) -> Result<&Self> {
1371        self.call_method(Runtime::Enable(None))?;
1372        Ok(self)
1373    }
1374
1375    /// Disables runtime domain
1376    pub fn disable_runtime(&self) -> Result<&Self> {
1377        self.call_method(Runtime::Disable(None))?;
1378        Ok(self)
1379    }
1380
1381    /// Enables Debugger
1382    pub fn enable_debugger(&self) -> Result<()> {
1383        self.call_method(Debugger::Enable {
1384            max_scripts_cache_size: None,
1385        })?;
1386        Ok(())
1387    }
1388
1389    /// Disables Debugger
1390    pub fn disable_debugger(&self) -> Result<()> {
1391        self.call_method(Debugger::Disable(None))?;
1392        Ok(())
1393    }
1394
1395    /// Returns source for the script with given id.
1396    ///
1397    /// Debugger must be enabled.
1398    pub fn get_script_source(&self, script_id: &str) -> Result<String> {
1399        Ok(self
1400            .call_method(Debugger::GetScriptSource {
1401                script_id: script_id.to_string(),
1402            })?
1403            .script_source)
1404    }
1405
1406    /// Enables log domain.
1407    ///
1408    /// Sends the entries collected so far to the client by means of the entryAdded notification.
1409    ///
1410    /// See <https://chromedevtools.github.io/devtools-protocol/tot/Log#method-enable>
1411    pub fn enable_log(&self) -> Result<&Self> {
1412        self.call_method(Log::Enable(None))?;
1413
1414        Ok(self)
1415    }
1416
1417    /// Disables log domain
1418    ///
1419    /// Prevents further log entries from being reported to the client
1420    ///
1421    /// See <https://chromedevtools.github.io/devtools-protocol/tot/Log#method-disable>
1422    pub fn disable_log(&self) -> Result<&Self> {
1423        self.call_method(Log::Disable(None))?;
1424
1425        Ok(self)
1426    }
1427
1428    /// Starts violation reporting
1429    ///
1430    /// See <https://chromedevtools.github.io/devtools-protocol/tot/Log#method-startViolationsReport>
1431    pub fn start_violations_report(&self, config: Vec<ViolationSetting>) -> Result<&Self> {
1432        self.call_method(Log::StartViolationsReport { config })?;
1433        Ok(self)
1434    }
1435
1436    /// Stop violation reporting
1437    ///
1438    /// See <https://chromedevtools.github.io/devtools-protocol/tot/Log#method-stopViolationsReport>
1439    pub fn stop_violations_report(&self) -> Result<&Self> {
1440        self.call_method(Log::StopViolationsReport(None))?;
1441        Ok(self)
1442    }
1443
1444    /// Evaluates expression on global object.
1445    pub fn evaluate(&self, expression: &str, await_promise: bool) -> Result<Runtime::RemoteObject> {
1446        let result = self
1447            .call_method(Runtime::Evaluate {
1448                expression: expression.to_string(),
1449                return_by_value: Some(false),
1450                generate_preview: Some(true),
1451                silent: Some(false),
1452                await_promise: Some(await_promise),
1453                include_command_line_api: Some(false),
1454                user_gesture: Some(false),
1455                object_group: None,
1456                context_id: None,
1457                throw_on_side_effect: None,
1458                timeout: None,
1459                disable_breaks: None,
1460                repl_mode: None,
1461                allow_unsafe_eval_blocked_by_csp: None,
1462                unique_context_id: None,
1463                serialization_options: None,
1464            })?
1465            .result;
1466        Ok(result)
1467    }
1468
1469    /// Adds event listener to Event
1470    ///
1471    /// Make sure you are enabled domain you are listening events to.
1472    ///
1473    /// ## Usage example
1474    ///
1475    /// ```rust
1476    /// # use anyhow::Result;
1477    /// # use std::sync::Arc;
1478    /// # fn main() -> Result<()> {
1479    /// #
1480    /// # use headless_chrome::Browser;
1481    /// # use headless_chrome::protocol::Event;
1482    /// # let browser = Browser::default()?;
1483    /// # let tab = browser.new_tab()?;
1484    /// tab.enable_log()?;
1485    /// tab.add_event_listener(Arc::new(move |event: &Event| {
1486    ///     match event {
1487    ///         Event::LogEntryAdded(_) => {
1488    ///             // process event here
1489    ///         }
1490    ///         _ => {}
1491    ///       }
1492    ///     }))?;
1493    /// #
1494    /// #     Ok(())
1495    /// # }
1496    /// ```
1497    ///
1498    pub fn add_event_listener(&self, listener: Arc<SyncSendEvent>) -> Result<Weak<SyncSendEvent>> {
1499        let mut listeners = self.event_listeners.lock().unwrap();
1500        listeners.push(listener);
1501        Ok(Arc::downgrade(listeners.last().unwrap()))
1502    }
1503
1504    pub fn remove_event_listener(&self, listener: &Weak<SyncSendEvent>) -> Result<()> {
1505        let listener = listener.upgrade();
1506        if listener.is_none() {
1507            return Ok(());
1508        }
1509        let listener = listener.unwrap();
1510        let mut listeners = self.event_listeners.lock().unwrap();
1511        let pos = listeners.iter().position(|x| Arc::ptr_eq(x, &listener));
1512        if let Some(idx) = pos {
1513            listeners.remove(idx);
1514        }
1515
1516        Ok(())
1517    }
1518
1519    /// Closes the target Page
1520    pub fn close_target(&self) -> Result<bool> {
1521        self.call_method(Target::CloseTarget {
1522            target_id: self.get_target_id().to_string(),
1523        })
1524        .map(|r| r.success)
1525    }
1526
1527    /// Tries to close page, running its beforeunload hooks, if any
1528    pub fn close_with_unload(&self) -> Result<bool> {
1529        self.call_method(Page::Close(None)).map(|_| true)
1530    }
1531
1532    /// Calls one of the close_* methods depending on fire_unload option
1533    pub fn close(&self, fire_unload: bool) -> Result<bool> {
1534        self.optional_slow_motion_sleep(50);
1535
1536        if fire_unload {
1537            return self.close_with_unload();
1538        }
1539        self.close_target()
1540    }
1541
1542    /// Activates (focuses) the target.
1543    pub fn activate(&self) -> Result<&Self> {
1544        self.call_method(Target::ActivateTarget {
1545            target_id: self.get_target_id().clone(),
1546        })
1547        .map(|_| self)
1548    }
1549
1550    /// Get position and size of the browser window associated with this `Tab`.
1551    ///
1552    /// Note that the returned bounds are always specified for normal (windowed)
1553    /// state; they do not change when minimizing, maximizing or setting to
1554    /// fullscreen.
1555    pub fn get_bounds(&self) -> Result<CurrentBounds, Error> {
1556        self.transport
1557            .call_method_on_browser(Browser::GetWindowForTarget {
1558                target_id: Some(self.get_target_id().to_string()),
1559            })
1560            .map(|r| r.bounds.into())
1561    }
1562
1563    /// Set position and/or size of the browser window associated with this `Tab`.
1564    ///
1565    /// When setting the window to normal (windowed) state, unspecified fields
1566    /// are left unchanged.
1567    pub fn set_bounds(&self, bounds: Bounds) -> Result<&Self, Error> {
1568        let window_id = self
1569            .transport
1570            .call_method_on_browser(Browser::GetWindowForTarget {
1571                target_id: Some(self.get_target_id().to_string()),
1572            })?
1573            .window_id;
1574        // If we set Normal window state, we *have* to make two API calls
1575        // to set the state before setting the coordinates; despite what the docs say...
1576        if let Bounds::Normal { .. } = &bounds {
1577            self.transport
1578                .call_method_on_browser(Browser::SetWindowBounds {
1579                    window_id,
1580                    bounds: Browser::Bounds {
1581                        left: None,
1582                        top: None,
1583                        width: None,
1584                        height: None,
1585                        window_state: Some(Browser::WindowState::Normal),
1586                    },
1587                })?;
1588        }
1589        self.transport
1590            .call_method_on_browser(Browser::SetWindowBounds {
1591                window_id,
1592                bounds: bounds.into(),
1593            })?;
1594        Ok(self)
1595    }
1596
1597    /// Returns all cookies that match the tab's current URL.
1598    pub fn get_cookies(&self) -> Result<Vec<Cookie>> {
1599        Ok(self
1600            .call_method(Network::GetCookies { urls: None })?
1601            .cookies)
1602    }
1603
1604    /// Set cookies with tab's current URL
1605    pub fn set_cookies(&self, cs: Vec<Network::CookieParam>) -> Result<()> {
1606        // puppeteer 7b24e5435b:src/common/Page.ts :1009-1028
1607        use Network::SetCookies;
1608        let url = self.get_url();
1609        let starts_with_http = url.starts_with("http");
1610        let cookies: Vec<Network::CookieParam> = cs
1611            .into_iter()
1612            .map(|c| {
1613                if c.url.is_none() && starts_with_http {
1614                    Network::CookieParam {
1615                        url: Some(url.clone()),
1616                        ..c
1617                    }
1618                } else {
1619                    c
1620                }
1621            })
1622            .collect();
1623        self.delete_cookies(
1624            cookies
1625                .clone()
1626                .into_iter()
1627                .map(std::convert::Into::into)
1628                .collect(),
1629        )?;
1630        self.call_method(SetCookies { cookies })?;
1631        Ok(())
1632    }
1633
1634    /// Delete cookies with tab's current URL
1635    pub fn delete_cookies(&self, cs: Vec<Network::DeleteCookies>) -> Result<()> {
1636        // puppeteer 7b24e5435b:src/common/Page.ts :998-1007
1637        let url = self.get_url();
1638        let starts_with_http = url.starts_with("http");
1639        cs.into_iter()
1640            .map(|c| {
1641                // REVIEW: if c.url is blank string
1642                if c.url.is_none() && starts_with_http {
1643                    Network::DeleteCookies {
1644                        url: Some(url.clone()),
1645                        ..c
1646                    }
1647                } else {
1648                    c
1649                }
1650            })
1651            .try_for_each(|c| -> Result<(), anyhow::Error> {
1652                let _ = self.call_method(c)?;
1653                Ok(())
1654            })?;
1655        Ok(())
1656    }
1657
1658    /// Returns the title of the document.
1659    ///
1660    /// ```rust
1661    /// # use anyhow::Result;
1662    /// # use headless_chrome::Browser;
1663    /// # fn main() -> Result<()> {
1664    /// #
1665    /// # let browser = Browser::default()?;
1666    /// # let tab = browser.new_tab()?;
1667    /// tab.navigate_to("https://google.com")?;
1668    /// tab.wait_until_navigated()?;
1669    /// let title = tab.get_title()?;
1670    /// assert_eq!(title, "Google");
1671    /// #
1672    /// # Ok(())
1673    /// # }
1674    /// ```
1675    pub fn get_title(&self) -> Result<String> {
1676        let remote_object = self.evaluate("document.title", false)?;
1677        Ok(serde_json::from_value(remote_object.value.unwrap())?)
1678    }
1679
1680    /// If enabled, instead of using the GUI to select files, the browser will
1681    /// wait for the `Tab.handle_file_chooser` method to be called.
1682    /// **WARNING**: Only works on Chromium / Chrome 77 and above.
1683    pub fn set_file_chooser_dialog_interception(&self, enabled: bool) -> Result<()> {
1684        self.call_method(SetInterceptFileChooserDialog { enabled })?;
1685        Ok(())
1686    }
1687
1688    /// Will have the same effect as choosing these files from the file chooser dialog that would've
1689    /// popped up had `set_file_chooser_dialog_interception` not been called. Calls to this method
1690    /// must be preceded by calls to that method.
1691    ///
1692    /// Supports selecting files or closing the file chooser dialog.
1693    ///
1694    /// NOTE: the filepaths listed in `files` must be absolute.
1695    pub fn handle_file_chooser(&self, files: Vec<String>, node_id: u32) -> Result<()> {
1696        self.call_method(DOM::SetFileInputFiles {
1697            files,
1698            node_id: Some(node_id),
1699            backend_node_id: None,
1700            object_id: None,
1701        })?;
1702        Ok(())
1703    }
1704
1705    pub fn set_extra_http_headers(&self, headers: HashMap<&str, &str>) -> Result<()> {
1706        self.call_method(Network::Enable {
1707            max_total_buffer_size: None,
1708            max_resource_buffer_size: None,
1709            max_post_data_size: None,
1710        })?;
1711        self.call_method(SetExtraHTTPHeaders {
1712            headers: Network::Headers(Some(json!(headers))),
1713        })?;
1714        Ok(())
1715    }
1716
1717    pub fn set_storage<T>(&self, item_name: &str, item: T) -> Result<()>
1718    where
1719        T: Serialize,
1720    {
1721        let value = json!(item).to_string();
1722
1723        self.evaluate(
1724            &format!(r#"localStorage.setItem("{item_name}",JSON.stringify({value}))"#),
1725            false,
1726        )?;
1727
1728        Ok(())
1729    }
1730
1731    pub fn get_storage<T>(&self, item_name: &str) -> Result<T>
1732    where
1733        T: DeserializeOwned,
1734    {
1735        let object = self.evaluate(&format!(r#"localStorage.getItem("{item_name}")"#), false)?;
1736
1737        let json: Option<T> = object.value.and_then(|v| match v {
1738            serde_json::Value::String(ref s) => {
1739                let result = serde_json::from_str(s);
1740
1741                if let Ok(r) = result {
1742                    Some(r)
1743                } else {
1744                    Some(serde_json::from_value(v).unwrap())
1745                }
1746            }
1747            _ => None,
1748        });
1749
1750        match json {
1751            Some(v) => Ok(v),
1752            None => Err(NoLocalStorageItemFound {}.into()),
1753        }
1754    }
1755
1756    pub fn remove_storage(&self, item_name: &str) -> Result<()> {
1757        self.evaluate(&format!(r#"localStorage.removeItem("{item_name}")"#), false)?;
1758
1759        Ok(())
1760    }
1761
1762    pub fn stop_loading(&self) -> Result<bool> {
1763        self.call_method(Page::StopLoading(None)).map(|_| true)
1764    }
1765
1766    fn bypass_user_agent(&self) -> Result<()> {
1767        let object = self.evaluate("window.navigator.userAgent", true)?;
1768
1769        match object.value.map(|x| x.to_string()) {
1770            Some(mut ua) => {
1771                ua = ua.replace("HeadlessChrome/", "Chrome/");
1772
1773                let re = regex::Regex::new(r"\(([^)]+)\)")?;
1774                ua = re.replace(&ua, "(Windows NT 10.0; Win64; x64)").to_string();
1775
1776                self.set_user_agent(&ua, None, None)?;
1777                Ok(())
1778            }
1779            None => Err(NoUserAgentEvaluated {}.into()),
1780        }
1781    }
1782
1783    fn bypass_wedriver(&self) -> Result<()> {
1784        self.call_method(Page::AddScriptToEvaluateOnNewDocument {
1785            source: "Object.defineProperty(navigator, 'webdriver', {get: () => undefined});"
1786                .to_string(),
1787            world_name: None,
1788            include_command_line_api: None,
1789            run_immediately: None,
1790        })?;
1791        Ok(())
1792    }
1793
1794    fn bypass_chrome(&self) -> Result<()> {
1795        self.call_method(Page::AddScriptToEvaluateOnNewDocument {
1796            source: "window.chrome = { runtime: {} };".to_string(),
1797            world_name: None,
1798            include_command_line_api: None,
1799            run_immediately: None,
1800        })?;
1801        Ok(())
1802    }
1803
1804    fn bypass_permissions(&self) -> Result<()> {
1805        let r = "const originalQuery = window.navigator.permissions.query;
1806        window.navigator.permissions.__proto__.query = parameters =>
1807        parameters.name === 'notifications'
1808            ? Promise.resolve({state: Notification.permission})
1809            : originalQuery(parameters);";
1810
1811        self.call_method(Page::AddScriptToEvaluateOnNewDocument {
1812            source: r.to_string(),
1813            world_name: None,
1814            include_command_line_api: None,
1815            run_immediately: None,
1816        })?;
1817        Ok(())
1818    }
1819
1820    fn bypass_plugins(&self) -> Result<()> {
1821        self.call_method(Page::AddScriptToEvaluateOnNewDocument {
1822            source: "Object.defineProperty(navigator, 'plugins', { get: () => [
1823            {filename:'internal-pdf-viewer'},
1824            {filename:'adsfkjlkjhalkh'},
1825            {filename:'internal-nacl-plugin'}
1826          ], });"
1827                .to_string(),
1828            world_name: None,
1829            include_command_line_api: None,
1830            run_immediately: None,
1831        })?;
1832        Ok(())
1833    }
1834
1835    fn bypass_webgl_vendor(&self) -> Result<()> {
1836        let r = "const getParameter = WebGLRenderingContext.getParameter;
1837        WebGLRenderingContext.prototype.getParameter = function(parameter) {
1838            // UNMASKED_VENDOR_WEBGL
1839            if (parameter === 37445) {
1840                return 'Google Inc. (NVIDIA)';
1841            }
1842            // UNMASKED_RENDERER_WEBGL
1843            if (parameter === 37446) {
1844                return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1050 Direct3D11 vs_5_0 ps_5_0, D3D11-27.21.14.5671)';
1845            }
1846
1847            return getParameter(parameter);
1848        };";
1849
1850        self.call_method(Page::AddScriptToEvaluateOnNewDocument {
1851            source: r.to_string(),
1852            world_name: None,
1853            include_command_line_api: None,
1854            run_immediately: None,
1855        })?;
1856        Ok(())
1857    }
1858
1859    pub fn enable_stealth_mode(&self) -> Result<()> {
1860        self.bypass_user_agent()?;
1861        self.bypass_wedriver()?;
1862        self.bypass_chrome()?;
1863        self.bypass_permissions()?;
1864        self.bypass_plugins()?;
1865        self.bypass_webgl_vendor()?;
1866        Ok(())
1867    }
1868
1869    pub fn start_screencast(
1870        &self,
1871        format: Option<Page::StartScreencastFormatOption>,
1872        quality: Option<u32>,
1873        max_width: Option<u32>,
1874        max_height: Option<u32>,
1875        every_nth_frame: Option<u32>,
1876    ) -> Result<()> {
1877        self.call_method(Page::StartScreencast {
1878            format,
1879            quality,
1880            max_width,
1881            max_height,
1882            every_nth_frame,
1883        })?;
1884        Ok(())
1885    }
1886
1887    pub fn stop_screencast(&self) -> Result<()> {
1888        self.call_method(Page::StopScreencast(None))?;
1889        Ok(())
1890    }
1891
1892    pub fn ack_screencast(&self, session_id: u32) -> Result<()> {
1893        self.call_method(Page::ScreencastFrameAck { session_id })?;
1894        Ok(())
1895    }
1896
1897    /// Get the handle of the dialog opened in this tab.
1898    pub fn get_dialog(&self) -> Dialog {
1899        Dialog::new(self.session_id.clone(), self.transport.clone())
1900    }
1901}