Skip to main content

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