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