chromiumoxide/handler/
mod.rs

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