chromiumoxide/handler/
mod.rs

1use crate::listeners::{EventListenerRequest, EventListeners};
2use chromiumoxide_cdp::cdp::browser_protocol::browser::*;
3use chromiumoxide_cdp::cdp::browser_protocol::target::*;
4use chromiumoxide_cdp::cdp::events::CdpEvent;
5use chromiumoxide_cdp::cdp::events::CdpEventMessage;
6use chromiumoxide_types::{CallId, Message, Method, Response};
7use chromiumoxide_types::{MethodId, Request as CdpRequest};
8use fnv::FnvHashMap;
9use futures::channel::mpsc::Receiver;
10use futures::channel::oneshot::Sender as OneshotSender;
11use futures::stream::{Fuse, Stream, StreamExt};
12use futures::task::{Context, Poll};
13use hashbrown::{HashMap, HashSet};
14pub(crate) use page::PageInner;
15use spider_network_blocker::intercept_manager::NetworkInterceptManager;
16use std::pin::Pin;
17use std::time::{Duration, Instant};
18use tokio_tungstenite::tungstenite::error::ProtocolError;
19use tokio_tungstenite::tungstenite::Error;
20
21use crate::cmd::{to_command_response, CommandMessage};
22use crate::conn::Connection;
23use crate::error::{CdpError, Result};
24use crate::handler::browser::BrowserContext;
25use crate::handler::frame::FrameRequestedNavigation;
26use crate::handler::frame::{NavigationError, NavigationId, NavigationOk};
27use crate::handler::job::PeriodicJob;
28use crate::handler::session::Session;
29use crate::handler::target::TargetEvent;
30use crate::handler::target::{Target, TargetConfig};
31use crate::handler::viewport::Viewport;
32use crate::page::Page;
33
34/// Standard timeout in MS
35pub const REQUEST_TIMEOUT: u64 = 30_000;
36
37pub mod blockers;
38pub mod browser;
39pub mod commandfuture;
40pub mod domworld;
41pub mod emulation;
42pub mod frame;
43pub mod http;
44pub mod httpfuture;
45mod job;
46pub mod network;
47mod page;
48mod session;
49pub mod target;
50pub mod target_message_future;
51pub mod viewport;
52
53/// The handler that monitors the state of the chromium browser and drives all
54/// the requests and events.
55#[must_use = "streams do nothing unless polled"]
56#[derive(Debug)]
57pub struct Handler {
58    pub default_browser_context: BrowserContext,
59    pub browser_contexts: HashSet<BrowserContext>,
60    /// Commands that are being processed and awaiting a response from the
61    /// chromium instance together with the timestamp when the request
62    /// started.
63    pending_commands: FnvHashMap<CallId, (PendingRequest, MethodId, Instant)>,
64    /// Connection to the browser instance
65    from_browser: Fuse<Receiver<HandlerMessage>>,
66    /// Used to loop over all targets in a consistent manner
67    target_ids: Vec<TargetId>,
68    /// The created and attached targets
69    targets: HashMap<TargetId, Target>,
70    /// Currently queued in navigations for targets
71    navigations: FnvHashMap<NavigationId, NavigationRequest>,
72    /// Keeps track of all the current active sessions
73    ///
74    /// There can be multiple sessions per target.
75    sessions: HashMap<SessionId, Session>,
76    /// The websocket connection to the chromium instance
77    conn: Connection<CdpEventMessage>,
78    /// Evicts timed out requests periodically
79    evict_command_timeout: PeriodicJob,
80    /// The internal identifier for a specific navigation
81    next_navigation_id: usize,
82    /// How this handler will configure targets etc,
83    config: HandlerConfig,
84    /// All registered event subscriptions
85    event_listeners: EventListeners,
86    /// Keeps track is the browser is closing
87    closing: bool,
88    /// Track the bytes remainder until network request will be blocked.
89    remaining_bytes: Option<u64>,
90    /// The budget is exhausted.
91    budget_exhausted: bool,
92}
93
94lazy_static::lazy_static! {
95    /// Set the discovery ID target.
96    static ref DISCOVER_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
97        let discover = SetDiscoverTargetsParams::new(true);
98        (discover.identifier(), serde_json::to_value(discover).expect("valid discover target params"))
99    };
100    /// Targets params id.
101    static ref TARGET_PARAMS_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
102        let msg = GetTargetsParams { filter: None };
103        (msg.identifier(), serde_json::to_value(msg).expect("valid paramtarget"))
104    };
105    /// Set the close targets.
106    static ref CLOSE_PARAMS_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
107        let close_msg = CloseParams::default();
108        (close_msg.identifier(), serde_json::to_value(close_msg).expect("valid close params"))
109    };
110}
111
112impl Handler {
113    /// Create a new `Handler` that drives the connection and listens for
114    /// messages on the receiver `rx`.
115    pub(crate) fn new(
116        mut conn: Connection<CdpEventMessage>,
117        rx: Receiver<HandlerMessage>,
118        config: HandlerConfig,
119    ) -> Self {
120        let discover = DISCOVER_ID.clone();
121        let _ = conn.submit_command(discover.0, None, discover.1);
122
123        let browser_contexts = config
124            .context_ids
125            .iter()
126            .map(|id| BrowserContext::from(id.clone()))
127            .collect();
128
129        Self {
130            pending_commands: Default::default(),
131            from_browser: rx.fuse(),
132            default_browser_context: Default::default(),
133            browser_contexts,
134            target_ids: Default::default(),
135            targets: Default::default(),
136            navigations: Default::default(),
137            sessions: Default::default(),
138            conn,
139            evict_command_timeout: PeriodicJob::new(config.request_timeout),
140            next_navigation_id: 0,
141            config,
142            event_listeners: Default::default(),
143            closing: false,
144            remaining_bytes: None,
145            budget_exhausted: false,
146        }
147    }
148
149    /// Return the target with the matching `target_id`
150    pub fn get_target(&self, target_id: &TargetId) -> Option<&Target> {
151        self.targets.get(target_id)
152    }
153
154    /// Iterator over all currently attached targets
155    pub fn targets(&self) -> impl Iterator<Item = &Target> + '_ {
156        self.targets.values()
157    }
158
159    /// The default Browser context
160    pub fn default_browser_context(&self) -> &BrowserContext {
161        &self.default_browser_context
162    }
163
164    /// Iterator over all currently available browser contexts
165    pub fn browser_contexts(&self) -> impl Iterator<Item = &BrowserContext> + '_ {
166        self.browser_contexts.iter()
167    }
168
169    /// received a response to a navigation request like `Page.navigate`
170    fn on_navigation_response(&mut self, id: NavigationId, resp: Response) {
171        if let Some(nav) = self.navigations.remove(&id) {
172            match nav {
173                NavigationRequest::Navigate(mut nav) => {
174                    if nav.navigated {
175                        let _ = nav.tx.send(Ok(resp));
176                    } else {
177                        nav.set_response(resp);
178                        self.navigations
179                            .insert(id, NavigationRequest::Navigate(nav));
180                    }
181                }
182            }
183        }
184    }
185
186    /// A navigation has finished.
187    fn on_navigation_lifecycle_completed(&mut self, res: Result<NavigationOk, NavigationError>) {
188        match res {
189            Ok(ok) => {
190                let id = *ok.navigation_id();
191                if let Some(nav) = self.navigations.remove(&id) {
192                    match nav {
193                        NavigationRequest::Navigate(mut nav) => {
194                            if let Some(resp) = nav.response.take() {
195                                let _ = nav.tx.send(Ok(resp));
196                            } else {
197                                nav.set_navigated();
198                                self.navigations
199                                    .insert(id, NavigationRequest::Navigate(nav));
200                            }
201                        }
202                    }
203                }
204            }
205            Err(err) => {
206                if let Some(nav) = self.navigations.remove(err.navigation_id()) {
207                    match nav {
208                        NavigationRequest::Navigate(nav) => {
209                            let _ = nav.tx.send(Err(err.into()));
210                        }
211                    }
212                }
213            }
214        }
215    }
216
217    /// Received a response to a request.
218    fn on_response(&mut self, resp: Response) {
219        if let Some((req, method, _)) = self.pending_commands.remove(&resp.id) {
220            match req {
221                PendingRequest::CreateTarget(tx) => {
222                    match to_command_response::<CreateTargetParams>(resp, method) {
223                        Ok(resp) => {
224                            if let Some(target) = self.targets.get_mut(&resp.target_id) {
225                                // move the sender to the target that sends its page once
226                                // initialized
227                                target.set_initiator(tx);
228                            }
229                        }
230                        Err(err) => {
231                            let _ = tx.send(Err(err)).ok();
232                        }
233                    }
234                }
235                PendingRequest::GetTargets(tx) => {
236                    match to_command_response::<GetTargetsParams>(resp, method) {
237                        Ok(resp) => {
238                            let targets: Vec<TargetInfo> = resp.result.target_infos;
239                            let results = targets.clone();
240                            for target_info in targets {
241                                let target_id = target_info.target_id.clone();
242                                let event: EventTargetCreated = EventTargetCreated { target_info };
243                                self.on_target_created(event);
244                                let attach = AttachToTargetParams::new(target_id);
245                                let method = attach.identifier();
246
247                                if let Ok(params) = serde_json::to_value(attach) {
248                                    let _ = self.conn.submit_command(method, None, params);
249                                }
250                            }
251
252                            let _ = tx.send(Ok(results)).ok();
253                        }
254                        Err(err) => {
255                            let _ = tx.send(Err(err)).ok();
256                        }
257                    }
258                }
259                PendingRequest::Navigate(id) => {
260                    self.on_navigation_response(id, resp);
261                    if self.config.only_html && !self.config.created_first_target {
262                        self.config.created_first_target = true;
263                    }
264                }
265                PendingRequest::ExternalCommand(tx) => {
266                    let _ = tx.send(Ok(resp)).ok();
267                }
268                PendingRequest::InternalCommand(target_id) => {
269                    if let Some(target) = self.targets.get_mut(&target_id) {
270                        target.on_response(resp, method.as_ref());
271                    }
272                }
273                PendingRequest::CloseBrowser(tx) => {
274                    self.closing = true;
275                    let _ = tx.send(Ok(CloseReturns {})).ok();
276                }
277            }
278        }
279    }
280
281    /// Submit a command initiated via channel
282    pub(crate) fn submit_external_command(
283        &mut self,
284        msg: CommandMessage,
285        now: Instant,
286    ) -> Result<()> {
287        let call_id = self
288            .conn
289            .submit_command(msg.method.clone(), msg.session_id, msg.params)?;
290        self.pending_commands.insert(
291            call_id,
292            (PendingRequest::ExternalCommand(msg.sender), msg.method, now),
293        );
294        Ok(())
295    }
296
297    pub(crate) fn submit_internal_command(
298        &mut self,
299        target_id: TargetId,
300        req: CdpRequest,
301        now: Instant,
302    ) -> Result<()> {
303        let call_id = self.conn.submit_command(
304            req.method.clone(),
305            req.session_id.map(Into::into),
306            req.params,
307        )?;
308        self.pending_commands.insert(
309            call_id,
310            (PendingRequest::InternalCommand(target_id), req.method, now),
311        );
312        Ok(())
313    }
314
315    fn submit_fetch_targets(&mut self, tx: OneshotSender<Result<Vec<TargetInfo>>>, now: Instant) {
316        let msg = TARGET_PARAMS_ID.clone();
317
318        if let Ok(call_id) = self.conn.submit_command(msg.0.clone(), None, msg.1) {
319            self.pending_commands
320                .insert(call_id, (PendingRequest::GetTargets(tx), msg.0, now));
321        }
322    }
323
324    /// Send the Request over to the server and store its identifier to handle
325    /// the response once received.
326    fn submit_navigation(&mut self, id: NavigationId, req: CdpRequest, now: Instant) {
327        if let Ok(call_id) = self.conn.submit_command(
328            req.method.clone(),
329            req.session_id.map(Into::into),
330            req.params,
331        ) {
332            self.pending_commands
333                .insert(call_id, (PendingRequest::Navigate(id), req.method, now));
334        }
335    }
336
337    fn submit_close(&mut self, tx: OneshotSender<Result<CloseReturns>>, now: Instant) {
338        let close_msg = CLOSE_PARAMS_ID.clone();
339
340        if let Ok(call_id) = self
341            .conn
342            .submit_command(close_msg.0.clone(), None, close_msg.1)
343        {
344            self.pending_commands.insert(
345                call_id,
346                (PendingRequest::CloseBrowser(tx), close_msg.0, now),
347            );
348        }
349    }
350
351    /// Process a message received by the target's page via channel
352    fn on_target_message(&mut self, target: &mut Target, msg: CommandMessage, now: Instant) {
353        if msg.is_navigation() {
354            let (req, tx) = msg.split();
355            let id = self.next_navigation_id();
356
357            target.goto(FrameRequestedNavigation::new(id, req));
358
359            self.navigations.insert(
360                id,
361                NavigationRequest::Navigate(NavigationInProgress::new(tx)),
362            );
363        } else {
364            let _ = self.submit_external_command(msg, now);
365        }
366    }
367
368    /// An identifier for queued `NavigationRequest`s.
369    fn next_navigation_id(&mut self) -> NavigationId {
370        let id = NavigationId(self.next_navigation_id);
371        self.next_navigation_id = self.next_navigation_id.wrapping_add(1);
372        id
373    }
374
375    /// Create a new page and send it to the receiver when ready
376    ///
377    /// First a `CreateTargetParams` is send to the server, this will trigger
378    /// `EventTargetCreated` which results in a new `Target` being created.
379    /// Once the response to the request is received the initialization process
380    /// of the target kicks in. This triggers a queue of initialization requests
381    /// of the `Target`, once those are all processed and the `url` fo the
382    /// `CreateTargetParams` has finished loading (The `Target`'s `Page` is
383    /// ready and idle), the `Target` sends its newly created `Page` as response
384    /// to the initiator (`tx`) of the `CreateTargetParams` request.
385    fn create_page(&mut self, params: CreateTargetParams, tx: OneshotSender<Result<Page>>) {
386        let about_blank = params.url == "about:blank";
387        let http_check =
388            !about_blank && params.url.starts_with("http") || params.url.starts_with("file://");
389
390        if about_blank || http_check {
391            let method = params.identifier();
392
393            match serde_json::to_value(params) {
394                Ok(params) => match self.conn.submit_command(method.clone(), None, params) {
395                    Ok(call_id) => {
396                        self.pending_commands.insert(
397                            call_id,
398                            (PendingRequest::CreateTarget(tx), method, Instant::now()),
399                        );
400                    }
401                    Err(err) => {
402                        let _ = tx.send(Err(err.into())).ok();
403                    }
404                },
405                Err(err) => {
406                    let _ = tx.send(Err(err.into())).ok();
407                }
408            }
409        } else {
410            let _ = tx.send(Err(CdpError::NotFound)).ok();
411        }
412    }
413
414    /// Process an incoming event read from the websocket
415    fn on_event(&mut self, event: CdpEventMessage) {
416        if let Some(ref session_id) = event.session_id {
417            if let Some(session) = self.sessions.get(session_id.as_str()) {
418                if let Some(target) = self.targets.get_mut(session.target_id()) {
419                    return target.on_event(event);
420                }
421            }
422        }
423        let CdpEventMessage { params, method, .. } = event;
424
425        match params {
426            CdpEvent::TargetTargetCreated(ref ev) => self.on_target_created(ev.clone()),
427            CdpEvent::TargetAttachedToTarget(ref ev) => self.on_attached_to_target(ev.clone()),
428            CdpEvent::TargetTargetDestroyed(ref ev) => self.on_target_destroyed(ev.clone()),
429            CdpEvent::TargetDetachedFromTarget(ref ev) => self.on_detached_from_target(ev.clone()),
430            _ => {}
431        }
432
433        chromiumoxide_cdp::consume_event!(match params {
434            |ev| self.event_listeners.start_send(ev),
435            |json| { let _ = self.event_listeners.try_send_custom(&method, json);}
436        });
437    }
438
439    /// Fired when a new target was created on the chromium instance
440    ///
441    /// Creates a new `Target` instance and keeps track of it
442    fn on_target_created(&mut self, event: EventTargetCreated) {
443        let browser_ctx = match event.target_info.browser_context_id {
444            Some(ref context_id) => {
445                let browser_context = BrowserContext {
446                    id: Some(context_id.clone()),
447                };
448                if self.default_browser_context.id.is_none() {
449                    self.default_browser_context = browser_context.clone();
450                };
451                self.browser_contexts.insert(browser_context.clone());
452
453                browser_context
454            }
455            _ => event
456                .target_info
457                .browser_context_id
458                .clone()
459                .map(BrowserContext::from)
460                .filter(|id| self.browser_contexts.contains(id))
461                .unwrap_or_else(|| self.default_browser_context.clone()),
462        };
463
464        let target = Target::new(
465            event.target_info,
466            TargetConfig {
467                ignore_https_errors: self.config.ignore_https_errors,
468                request_timeout: self.config.request_timeout,
469                viewport: self.config.viewport.clone(),
470                request_intercept: self.config.request_intercept,
471                cache_enabled: self.config.cache_enabled,
472                service_worker_enabled: self.config.service_worker_enabled,
473                ignore_visuals: self.config.ignore_visuals,
474                ignore_stylesheets: self.config.ignore_stylesheets,
475                ignore_javascript: self.config.ignore_javascript,
476                ignore_analytics: self.config.ignore_analytics,
477                extra_headers: self.config.extra_headers.clone(),
478                only_html: self.config.only_html && self.config.created_first_target,
479                intercept_manager: self.config.intercept_manager,
480                max_bytes_allowed: self.config.max_bytes_allowed,
481            },
482            browser_ctx,
483        );
484
485        self.target_ids.push(target.target_id().clone());
486        self.targets.insert(target.target_id().clone(), target);
487    }
488
489    /// A new session is attached to a target
490    fn on_attached_to_target(&mut self, event: Box<EventAttachedToTarget>) {
491        let session = Session::new(event.session_id.clone(), event.target_info.target_id);
492        if let Some(target) = self.targets.get_mut(session.target_id()) {
493            target.set_session_id(session.session_id().clone())
494        }
495        self.sessions.insert(event.session_id, session);
496    }
497
498    /// The session was detached from target.
499    /// Can be issued multiple times per target if multiple session have been
500    /// attached to it.
501    fn on_detached_from_target(&mut self, event: EventDetachedFromTarget) {
502        // remove the session
503        if let Some(session) = self.sessions.remove(&event.session_id) {
504            if let Some(target) = self.targets.get_mut(session.target_id()) {
505                target.session_id().take();
506            }
507        }
508    }
509
510    /// Fired when the target was destroyed in the browser
511    fn on_target_destroyed(&mut self, event: EventTargetDestroyed) {
512        if let Some(target) = self.targets.remove(&event.target_id) {
513            // TODO shutdown?
514            if let Some(session) = target.session_id() {
515                self.sessions.remove(session);
516            }
517        }
518    }
519
520    /// House keeping of commands
521    ///
522    /// Remove all commands where `now` > `timestamp of command starting point +
523    /// request timeout` and notify the senders that their request timed out.
524    fn evict_timed_out_commands(&mut self, now: Instant) {
525        let timed_out = self
526            .pending_commands
527            .iter()
528            .filter(|(_, (_, _, timestamp))| now > (*timestamp + self.config.request_timeout))
529            .map(|(k, _)| *k)
530            .collect::<Vec<_>>();
531
532        for call in timed_out {
533            if let Some((req, _, _)) = self.pending_commands.remove(&call) {
534                match req {
535                    PendingRequest::CreateTarget(tx) => {
536                        let _ = tx.send(Err(CdpError::Timeout));
537                    }
538                    PendingRequest::GetTargets(tx) => {
539                        let _ = tx.send(Err(CdpError::Timeout));
540                    }
541                    PendingRequest::Navigate(nav) => {
542                        if let Some(nav) = self.navigations.remove(&nav) {
543                            match nav {
544                                NavigationRequest::Navigate(nav) => {
545                                    let _ = nav.tx.send(Err(CdpError::Timeout));
546                                }
547                            }
548                        }
549                    }
550                    PendingRequest::ExternalCommand(tx) => {
551                        let _ = tx.send(Err(CdpError::Timeout));
552                    }
553                    PendingRequest::InternalCommand(_) => {}
554                    PendingRequest::CloseBrowser(tx) => {
555                        let _ = tx.send(Err(CdpError::Timeout));
556                    }
557                }
558            }
559        }
560    }
561
562    pub fn event_listeners_mut(&mut self) -> &mut EventListeners {
563        &mut self.event_listeners
564    }
565}
566
567impl Stream for Handler {
568    type Item = Result<()>;
569
570    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
571        let pin = self.get_mut();
572
573        let mut dispose = false;
574
575        loop {
576            let now = Instant::now();
577            // temporary pinning of the browser receiver should be safe as we are pinning
578            // through the already pinned self. with the receivers we can also
579            // safely ignore exhaustion as those are fused.
580            while let Poll::Ready(Some(msg)) = Pin::new(&mut pin.from_browser).poll_next(cx) {
581                match msg {
582                    HandlerMessage::Command(cmd) => {
583                        pin.submit_external_command(cmd, now)?;
584                    }
585                    HandlerMessage::FetchTargets(tx) => {
586                        pin.submit_fetch_targets(tx, now);
587                    }
588                    HandlerMessage::CloseBrowser(tx) => {
589                        pin.submit_close(tx, now);
590                    }
591                    HandlerMessage::CreatePage(params, tx) => {
592                        pin.create_page(params, tx);
593                    }
594                    HandlerMessage::GetPages(tx) => {
595                        let pages: Vec<_> = pin
596                            .targets
597                            .values_mut()
598                            .filter(|p: &&mut Target| p.is_page())
599                            .filter_map(|target| target.get_or_create_page())
600                            .map(|page| Page::from(page.clone()))
601                            .collect();
602                        let _ = tx.send(pages);
603                    }
604                    HandlerMessage::InsertContext(ctx) => {
605                        pin.default_browser_context = ctx.clone();
606                        pin.browser_contexts.insert(ctx);
607                    }
608                    HandlerMessage::DisposeContext(ctx) => {
609                        pin.browser_contexts.remove(&ctx);
610                        pin.closing = true;
611                        dispose = true;
612                    }
613                    HandlerMessage::GetPage(target_id, tx) => {
614                        let page = pin
615                            .targets
616                            .get_mut(&target_id)
617                            .and_then(|target| target.get_or_create_page())
618                            .map(|page| Page::from(page.clone()));
619                        let _ = tx.send(page);
620                    }
621                    HandlerMessage::AddEventListener(req) => {
622                        pin.event_listeners.add_listener(req);
623                    }
624                }
625            }
626
627            for n in (0..pin.target_ids.len()).rev() {
628                let target_id = pin.target_ids.swap_remove(n);
629
630                if let Some((id, mut target)) = pin.targets.remove_entry(&target_id) {
631                    while let Some(event) = target.poll(cx, now) {
632                        match event {
633                            TargetEvent::Request(req) => {
634                                let _ = pin.submit_internal_command(
635                                    target.target_id().clone(),
636                                    req,
637                                    now,
638                                );
639                            }
640                            TargetEvent::Command(msg) => {
641                                pin.on_target_message(&mut target, msg, now);
642                            }
643                            TargetEvent::NavigationRequest(id, req) => {
644                                pin.submit_navigation(id, req, now);
645                            }
646                            TargetEvent::NavigationResult(res) => {
647                                pin.on_navigation_lifecycle_completed(res)
648                            }
649                            TargetEvent::BytesConsumed(n) => {
650                                if let Some(rem) = pin.remaining_bytes.as_mut() {
651                                    *rem = rem.saturating_sub(n);
652                                    if *rem == 0 {
653                                        pin.budget_exhausted = true;
654                                    }
655                                }
656                            }
657                        }
658                    }
659
660                    // poll the target's event listeners
661                    target.event_listeners_mut().poll(cx);
662                    // poll the handler's event listeners
663                    pin.event_listeners_mut().poll(cx);
664
665                    pin.targets.insert(id, target);
666                    pin.target_ids.push(target_id);
667                }
668            }
669
670            let mut done = true;
671
672            while let Poll::Ready(Some(ev)) = Pin::new(&mut pin.conn).poll_next(cx) {
673                match ev {
674                    Ok(boxed_msg) => match *boxed_msg {
675                        Message::Response(resp) => {
676                            pin.on_response(resp);
677                            if pin.closing {
678                                return Poll::Ready(None);
679                            }
680                        }
681                        Message::Event(ev) => {
682                            pin.on_event(ev);
683                        }
684                    },
685                    Err(err) => {
686                        tracing::error!("WS Connection error: {:?}", err);
687                        match err {
688                            CdpError::Ws(ref ws_error) => match ws_error {
689                                Error::AlreadyClosed => {
690                                    pin.closing = true;
691                                    dispose = true;
692                                    break;
693                                }
694                                Error::Protocol(detail)
695                                    if detail == &ProtocolError::ResetWithoutClosingHandshake =>
696                                {
697                                    pin.closing = true;
698                                    dispose = true;
699                                    break;
700                                }
701                                _ => {}
702                            },
703                            _ => {}
704                        };
705                        return Poll::Ready(Some(Err(err)));
706                    }
707                }
708                done = false;
709            }
710
711            if pin.evict_command_timeout.poll_ready(cx) {
712                // evict all commands that timed out
713                pin.evict_timed_out_commands(now);
714            }
715
716            if pin.budget_exhausted {
717                for t in pin.targets.values_mut() {
718                    t.network_manager.set_block_all(true);
719                }
720            }
721
722            if dispose {
723                return Poll::Ready(None);
724            }
725
726            if done {
727                // no events/responses were read from the websocket
728                return Poll::Pending;
729            }
730        }
731    }
732}
733
734/// How to configure the handler
735#[derive(Debug, Clone)]
736pub struct HandlerConfig {
737    /// Whether the `NetworkManager`s should ignore https errors
738    pub ignore_https_errors: bool,
739    /// Window and device settings
740    pub viewport: Option<Viewport>,
741    /// Context ids to set from the get go
742    pub context_ids: Vec<BrowserContextId>,
743    /// default request timeout to use
744    pub request_timeout: Duration,
745    /// Whether to enable request interception
746    pub request_intercept: bool,
747    /// Whether to enable cache
748    pub cache_enabled: bool,
749    /// Whether to enable Service Workers
750    pub service_worker_enabled: bool,
751    /// Whether to ignore visuals.
752    pub ignore_visuals: bool,
753    /// Whether to ignore stylesheets.
754    pub ignore_stylesheets: bool,
755    /// Whether to ignore Javascript only allowing critical framework or lib based rendering.
756    pub ignore_javascript: bool,
757    /// Whether to ignore analytics.
758    pub ignore_analytics: bool,
759    /// Whether to ignore ads.
760    pub ignore_ads: bool,
761    /// Extra headers.
762    pub extra_headers: Option<std::collections::HashMap<String, String>>,
763    /// Only Html.
764    pub only_html: bool,
765    /// Created the first target.
766    pub created_first_target: bool,
767    /// The network intercept manager.
768    pub intercept_manager: NetworkInterceptManager,
769    /// The max bytes to receive.
770    pub max_bytes_allowed: Option<u64>,
771}
772
773impl Default for HandlerConfig {
774    fn default() -> Self {
775        Self {
776            ignore_https_errors: true,
777            viewport: Default::default(),
778            context_ids: Vec::new(),
779            request_timeout: Duration::from_millis(REQUEST_TIMEOUT),
780            request_intercept: false,
781            cache_enabled: true,
782            service_worker_enabled: true,
783            ignore_visuals: false,
784            ignore_stylesheets: false,
785            ignore_ads: false,
786            ignore_javascript: false,
787            ignore_analytics: true,
788            only_html: false,
789            extra_headers: Default::default(),
790            created_first_target: false,
791            intercept_manager: NetworkInterceptManager::Unknown,
792            max_bytes_allowed: None,
793        }
794    }
795}
796
797/// Wraps the sender half of the channel who requested a navigation
798#[derive(Debug)]
799pub struct NavigationInProgress<T> {
800    /// Marker to indicate whether a navigation lifecycle has completed
801    navigated: bool,
802    /// The response of the issued navigation request
803    response: Option<Response>,
804    /// Sender who initiated the navigation request
805    tx: OneshotSender<T>,
806}
807
808impl<T> NavigationInProgress<T> {
809    fn new(tx: OneshotSender<T>) -> Self {
810        Self {
811            navigated: false,
812            response: None,
813            tx,
814        }
815    }
816
817    /// The response to the cdp request has arrived
818    fn set_response(&mut self, resp: Response) {
819        self.response = Some(resp);
820    }
821
822    /// The navigation process has finished, the page finished loading.
823    fn set_navigated(&mut self) {
824        self.navigated = true;
825    }
826}
827
828/// Request type for navigation
829#[derive(Debug)]
830enum NavigationRequest {
831    /// Represents a simple `NavigateParams` ("Page.navigate")
832    Navigate(NavigationInProgress<Result<Response>>),
833    // TODO are there more?
834}
835
836/// Different kind of submitted request submitted from the  `Handler` to the
837/// `Connection` and being waited on for the response.
838#[derive(Debug)]
839enum PendingRequest {
840    /// A Request to create a new `Target` that results in the creation of a
841    /// `Page` that represents a browser page.
842    CreateTarget(OneshotSender<Result<Page>>),
843    /// A Request to fetch old `Target`s created before connection
844    GetTargets(OneshotSender<Result<Vec<TargetInfo>>>),
845    /// A Request to navigate a specific `Target`.
846    ///
847    /// Navigation requests are not automatically completed once the response to
848    /// the raw cdp navigation request (like `NavigateParams`) arrives, but only
849    /// after the `Target` notifies the `Handler` that the `Page` has finished
850    /// loading, which comes after the response.
851    Navigate(NavigationId),
852    /// A common request received via a channel (`Page`).
853    ExternalCommand(OneshotSender<Result<Response>>),
854    /// Requests that are initiated directly from a `Target` (all the
855    /// initialization commands).
856    InternalCommand(TargetId),
857    // A Request to close the browser.
858    CloseBrowser(OneshotSender<Result<CloseReturns>>),
859}
860
861/// Events used internally to communicate with the handler, which are executed
862/// in the background
863// TODO rename to BrowserMessage
864#[derive(Debug)]
865pub(crate) enum HandlerMessage {
866    CreatePage(CreateTargetParams, OneshotSender<Result<Page>>),
867    FetchTargets(OneshotSender<Result<Vec<TargetInfo>>>),
868    InsertContext(BrowserContext),
869    DisposeContext(BrowserContext),
870    GetPages(OneshotSender<Vec<Page>>),
871    Command(CommandMessage),
872    GetPage(TargetId, OneshotSender<Option<Page>>),
873    AddEventListener(EventListenerRequest),
874    CloseBrowser(OneshotSender<Result<CloseReturns>>),
875}