1use crate::handler::blockers::intercept_manager::NetworkInterceptManager;
2use hashbrown::{HashMap, HashSet};
3use std::pin::Pin;
4use std::time::{Duration, Instant};
5
6use fnv::FnvHashMap;
7use futures::channel::mpsc::Receiver;
8use futures::channel::oneshot::Sender as OneshotSender;
9use futures::stream::{Fuse, Stream, StreamExt};
10use futures::task::{Context, Poll};
11
12use crate::listeners::{EventListenerRequest, EventListeners};
13use chromiumoxide_cdp::cdp::browser_protocol::browser::*;
14use chromiumoxide_cdp::cdp::browser_protocol::target::*;
15use chromiumoxide_cdp::cdp::events::CdpEvent;
16use chromiumoxide_cdp::cdp::events::CdpEventMessage;
17use chromiumoxide_types::{CallId, Message, Method, Response};
18use chromiumoxide_types::{MethodId, Request as CdpRequest};
19pub(crate) use page::PageInner;
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
34pub const REQUEST_TIMEOUT: u64 = 60_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#[must_use = "streams do nothing unless polled"]
56#[derive(Debug)]
57pub struct Handler {
58 pending_commands: FnvHashMap<CallId, (PendingRequest, MethodId, Instant)>,
62 from_browser: Fuse<Receiver<HandlerMessage>>,
64 pub default_browser_context: BrowserContext,
65 pub browser_contexts: HashSet<BrowserContext>,
66 target_ids: Vec<TargetId>,
68 targets: HashMap<TargetId, Target>,
70 navigations: FnvHashMap<NavigationId, NavigationRequest>,
72 sessions: HashMap<SessionId, Session>,
76 conn: Connection<CdpEventMessage>,
78 evict_command_timeout: PeriodicJob,
80 next_navigation_id: usize,
82 config: HandlerConfig,
84 event_listeners: EventListeners,
86 closing: bool,
88}
89
90impl Handler {
91 pub(crate) fn new(
94 mut conn: Connection<CdpEventMessage>,
95 rx: Receiver<HandlerMessage>,
96 config: HandlerConfig,
97 ) -> Self {
98 let discover = SetDiscoverTargetsParams::new(true);
99 let discover_id = discover.identifier();
100
101 if let Ok(params) = serde_json::to_value(discover) {
102 let _ = conn.submit_command(discover_id, None, params);
103 }
104
105 let browser_contexts = config
106 .context_ids
107 .iter()
108 .map(|id| BrowserContext::from(id.clone()))
109 .collect();
110
111 Self {
112 pending_commands: Default::default(),
113 from_browser: rx.fuse(),
114 default_browser_context: Default::default(),
115 browser_contexts,
116 target_ids: Default::default(),
117 targets: Default::default(),
118 navigations: Default::default(),
119 sessions: Default::default(),
120 conn,
121 evict_command_timeout: PeriodicJob::new(config.request_timeout),
122 next_navigation_id: 0,
123 config,
124 event_listeners: Default::default(),
125 closing: false,
126 }
127 }
128
129 pub fn get_target(&self, target_id: &TargetId) -> Option<&Target> {
131 self.targets.get(target_id)
132 }
133
134 pub fn targets(&self) -> impl Iterator<Item = &Target> + '_ {
136 self.targets.values()
137 }
138
139 pub fn default_browser_context(&self) -> &BrowserContext {
141 &self.default_browser_context
142 }
143
144 pub fn set_default_browser_context(&mut self, context_id: BrowserContextId) -> &BrowserContext {
146 let browser_context = BrowserContext {
147 id: Some(context_id),
148 };
149 self.browser_contexts.insert(browser_context.clone());
150 self.default_browser_context = browser_context;
151 &self.default_browser_context
152 }
153
154 pub fn browser_contexts(&self) -> impl Iterator<Item = &BrowserContext> + '_ {
156 self.browser_contexts.iter()
157 }
158
159 fn on_navigation_response(&mut self, id: NavigationId, resp: Response) {
161 if let Some(nav) = self.navigations.remove(&id) {
162 match nav {
163 NavigationRequest::Navigate(mut nav) => {
164 if nav.navigated {
165 let _ = nav.tx.send(Ok(resp));
166 } else {
167 nav.set_response(resp);
168 self.navigations
169 .insert(id, NavigationRequest::Navigate(nav));
170 }
171 }
172 }
173 }
174 }
175
176 fn on_navigation_lifecycle_completed(&mut self, res: Result<NavigationOk, NavigationError>) {
178 match res {
179 Ok(ok) => {
180 let id = *ok.navigation_id();
181 if let Some(nav) = self.navigations.remove(&id) {
182 match nav {
183 NavigationRequest::Navigate(mut nav) => {
184 if let Some(resp) = nav.response.take() {
185 let _ = nav.tx.send(Ok(resp));
186 } else {
187 nav.set_navigated();
188 self.navigations
189 .insert(id, NavigationRequest::Navigate(nav));
190 }
191 }
192 }
193 }
194 }
195 Err(err) => {
196 if let Some(nav) = self.navigations.remove(err.navigation_id()) {
197 match nav {
198 NavigationRequest::Navigate(nav) => {
199 let _ = nav.tx.send(Err(err.into()));
200 }
201 }
202 }
203 }
204 }
205 }
206
207 fn on_response(&mut self, resp: Response) {
209 if let Some((req, method, _)) = self.pending_commands.remove(&resp.id) {
210 match req {
211 PendingRequest::CreateTarget(tx) => {
212 match to_command_response::<CreateTargetParams>(resp, method) {
213 Ok(resp) => {
214 if let Some(target) = self.targets.get_mut(&resp.target_id) {
215 target.set_initiator(tx);
218 }
219 }
220 Err(err) => {
221 let _ = tx.send(Err(err)).ok();
222 }
223 }
224 }
225 PendingRequest::GetTargets(tx) => {
226 match to_command_response::<GetTargetsParams>(resp, method) {
227 Ok(resp) => {
228 let targets: Vec<TargetInfo> = resp.result.target_infos;
229 let results = targets.clone();
230 for target_info in targets {
231 let target_id = target_info.target_id.clone();
232 let event: EventTargetCreated = EventTargetCreated { target_info };
233 self.on_target_created(event);
234 let attach = AttachToTargetParams::new(target_id);
235
236 let _ = self.conn.submit_command(
237 attach.identifier(),
238 None,
239 serde_json::to_value(attach).unwrap_or_default(),
240 );
241 }
242
243 let _ = tx.send(Ok(results)).ok();
244 }
245 Err(err) => {
246 let _ = tx.send(Err(err)).ok();
247 }
248 }
249 }
250 PendingRequest::Navigate(id) => {
251 self.on_navigation_response(id, resp);
252 if self.config.only_html && !self.config.created_first_target {
253 self.config.created_first_target = true;
254 }
255 }
256 PendingRequest::ExternalCommand(tx) => {
257 let _ = tx.send(Ok(resp)).ok();
258 }
259 PendingRequest::InternalCommand(target_id) => {
260 if let Some(target) = self.targets.get_mut(&target_id) {
261 target.on_response(resp, method.as_ref());
262 }
263 }
264 PendingRequest::CloseBrowser(tx) => {
265 self.closing = true;
266 let _ = tx.send(Ok(CloseReturns {})).ok();
267 }
268 }
269 }
270 }
271
272 pub(crate) fn submit_external_command(
274 &mut self,
275 msg: CommandMessage,
276 now: Instant,
277 ) -> Result<()> {
278 let call_id = self
279 .conn
280 .submit_command(msg.method.clone(), msg.session_id, msg.params)?;
281 self.pending_commands.insert(
282 call_id,
283 (PendingRequest::ExternalCommand(msg.sender), msg.method, now),
284 );
285 Ok(())
286 }
287
288 pub(crate) fn submit_internal_command(
289 &mut self,
290 target_id: TargetId,
291 req: CdpRequest,
292 now: Instant,
293 ) -> Result<()> {
294 let call_id = self.conn.submit_command(
295 req.method.clone(),
296 req.session_id.map(Into::into),
297 req.params,
298 )?;
299 self.pending_commands.insert(
300 call_id,
301 (PendingRequest::InternalCommand(target_id), req.method, now),
302 );
303 Ok(())
304 }
305
306 fn submit_fetch_targets(&mut self, tx: OneshotSender<Result<Vec<TargetInfo>>>, now: Instant) {
307 let msg = GetTargetsParams { filter: None };
308 let method = msg.identifier();
309
310 if let Ok(params) = serde_json::to_value(msg) {
311 if let Ok(call_id) = self.conn.submit_command(method.clone(), None, params) {
312 self.pending_commands
313 .insert(call_id, (PendingRequest::GetTargets(tx), method, now));
314 }
315 }
316 }
317
318 fn submit_navigation(&mut self, id: NavigationId, req: CdpRequest, now: Instant) {
321 if let Ok(call_id) = self.conn.submit_command(
322 req.method.clone(),
323 req.session_id.map(Into::into),
324 req.params,
325 ) {
326 self.pending_commands
327 .insert(call_id, (PendingRequest::Navigate(id), req.method, now));
328 }
329 }
330
331 fn submit_close(&mut self, tx: OneshotSender<Result<CloseReturns>>, now: Instant) {
332 let close_msg = CloseParams::default();
333 let method = close_msg.identifier();
334
335 if let Ok(call_id) = self.conn.submit_command(
336 method.clone(),
337 None,
338 serde_json::to_value(close_msg).unwrap(),
339 ) {
340 self.pending_commands
341 .insert(call_id, (PendingRequest::CloseBrowser(tx), method, now));
342 }
343 }
344
345 fn on_target_message(&mut self, target: &mut Target, msg: CommandMessage, now: Instant) {
347 if msg.is_navigation() {
349 let (req, tx) = msg.split();
350 let id = self.next_navigation_id();
351 target.goto(FrameRequestedNavigation::new(id, req));
352 self.navigations.insert(
353 id,
354 NavigationRequest::Navigate(NavigationInProgress::new(tx)),
355 );
356 } else {
357 let _ = self.submit_external_command(msg, now);
358 }
359 }
360
361 fn next_navigation_id(&mut self) -> NavigationId {
363 let id = NavigationId(self.next_navigation_id);
364 self.next_navigation_id = self.next_navigation_id.wrapping_add(1);
365 id
366 }
367
368 fn create_page(&mut self, params: CreateTargetParams, tx: OneshotSender<Result<Page>>) {
379 let about_blank = params.url == "about:blank";
380 let http_check =
381 !about_blank && params.url.starts_with("http") || params.url.starts_with("file://");
382
383 if about_blank || http_check {
384 let method = params.identifier();
385
386 match serde_json::to_value(params) {
387 Ok(params) => match self.conn.submit_command(method.clone(), None, params) {
388 Ok(call_id) => {
389 self.pending_commands.insert(
390 call_id,
391 (PendingRequest::CreateTarget(tx), method, Instant::now()),
392 );
393 }
394 Err(err) => {
395 let _ = tx.send(Err(err.into())).ok();
396 }
397 },
398 Err(err) => {
399 let _ = tx.send(Err(err.into())).ok();
400 }
401 }
402 } else {
403 let _ = tx.send(Err(CdpError::NotFound)).ok();
404 }
405 }
406
407 fn on_event(&mut self, event: CdpEventMessage) {
409 if let Some(ref session_id) = event.session_id {
410 if let Some(session) = self.sessions.get(session_id.as_str()) {
411 if let Some(target) = self.targets.get_mut(session.target_id()) {
412 return target.on_event(event);
413 }
414 }
415 }
416 let CdpEventMessage { params, method, .. } = event;
417
418 match params {
419 CdpEvent::TargetTargetCreated(ref ev) => self.on_target_created(ev.clone()),
420 CdpEvent::TargetAttachedToTarget(ref ev) => self.on_attached_to_target(ev.clone()),
421 CdpEvent::TargetTargetDestroyed(ref ev) => self.on_target_destroyed(ev.clone()),
422 CdpEvent::TargetDetachedFromTarget(ref ev) => self.on_detached_from_target(ev.clone()),
423 _ => {}
424 }
425
426 chromiumoxide_cdp::consume_event!(match params {
427 |ev| self.event_listeners.start_send(ev),
428 |json| { let _ = self.event_listeners.try_send_custom(&method, json);}
429 });
430 }
431
432 fn on_target_created(&mut self, event: EventTargetCreated) {
436 let browser_ctx = match event.target_info.browser_context_id {
437 Some(ref context_id) => {
438 let browser_context = BrowserContext {
439 id: Some(context_id.clone()),
440 };
441 if self.default_browser_context.id.is_none() {
442 self.default_browser_context = browser_context.clone();
443 };
444 self.browser_contexts.insert(browser_context.clone());
445
446 browser_context
447 }
448 _ => event
449 .target_info
450 .browser_context_id
451 .clone()
452 .map(BrowserContext::from)
453 .filter(|id| self.browser_contexts.contains(id))
454 .unwrap_or_else(|| self.default_browser_context.clone()),
455 };
456
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 extra_headers: self.config.extra_headers.clone(),
471 only_html: self.config.only_html && self.config.created_first_target,
472 intercept_manager: self.config.intercept_manager,
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 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 fn on_detached_from_target(&mut self, event: EventDetachedFromTarget) {
494 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 fn on_target_destroyed(&mut self, event: EventTargetDestroyed) {
504 if let Some(target) = self.targets.remove(&event.target_id) {
505 if let Some(session) = target.session_id() {
507 self.sessions.remove(session);
508 }
509 }
510 }
511
512 fn evict_timed_out_commands(&mut self, now: Instant) {
517 let timed_out = self
518 .pending_commands
519 .iter()
520 .filter(|(_, (_, _, timestamp))| now > (*timestamp + self.config.request_timeout))
521 .map(|(k, _)| *k)
522 .collect::<Vec<_>>();
523
524 for call in timed_out {
525 if let Some((req, _, _)) = self.pending_commands.remove(&call) {
526 match req {
527 PendingRequest::CreateTarget(tx) => {
528 let _ = tx.send(Err(CdpError::Timeout));
529 }
530 PendingRequest::GetTargets(tx) => {
531 let _ = tx.send(Err(CdpError::Timeout));
532 }
533 PendingRequest::Navigate(nav) => {
534 if let Some(nav) = self.navigations.remove(&nav) {
535 match nav {
536 NavigationRequest::Navigate(nav) => {
537 let _ = nav.tx.send(Err(CdpError::Timeout));
538 }
539 }
540 }
541 }
542 PendingRequest::ExternalCommand(tx) => {
543 let _ = tx.send(Err(CdpError::Timeout));
544 }
545 PendingRequest::InternalCommand(_) => {}
546 PendingRequest::CloseBrowser(tx) => {
547 let _ = tx.send(Err(CdpError::Timeout));
548 }
549 }
550 }
551 }
552 }
553
554 pub fn event_listeners_mut(&mut self) -> &mut EventListeners {
555 &mut self.event_listeners
556 }
557}
558
559impl Stream for Handler {
560 type Item = Result<()>;
561
562 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
563 let pin = self.get_mut();
564
565 loop {
566 let now = Instant::now();
567 while let Poll::Ready(Some(msg)) = Pin::new(&mut pin.from_browser).poll_next(cx) {
571 match msg {
572 HandlerMessage::Command(cmd) => {
573 pin.submit_external_command(cmd, now)?;
574 }
575 HandlerMessage::FetchTargets(tx) => {
576 pin.submit_fetch_targets(tx, now);
577 }
578 HandlerMessage::CloseBrowser(tx) => {
579 pin.submit_close(tx, now);
580 }
581 HandlerMessage::CreatePage(params, tx) => {
582 pin.create_page(params, tx);
583 }
584 HandlerMessage::GetPages(tx) => {
585 let pages: Vec<_> = pin
586 .targets
587 .values_mut()
588 .filter(|p: &&mut Target| p.is_page())
589 .filter_map(|target| target.get_or_create_page())
590 .map(|page| Page::from(page.clone()))
591 .collect();
592 let _ = tx.send(pages);
593 }
594 HandlerMessage::InsertContext(ctx) => {
595 if pin.default_browser_context.id.is_none() {
596 pin.default_browser_context = ctx.clone();
597 }
598 pin.browser_contexts.insert(ctx);
599 }
600 HandlerMessage::DisposeContext(ctx) => {
601 pin.browser_contexts.remove(&ctx);
602 }
603 HandlerMessage::GetPage(target_id, tx) => {
604 let page = pin
605 .targets
606 .get_mut(&target_id)
607 .and_then(|target| target.get_or_create_page())
608 .map(|page| Page::from(page.clone()));
609 let _ = tx.send(page);
610 }
611 HandlerMessage::AddEventListener(req) => {
612 pin.event_listeners.add_listener(req);
613 }
614 }
615 }
616
617 for n in (0..pin.target_ids.len()).rev() {
618 let target_id = pin.target_ids.swap_remove(n);
619
620 if let Some((id, mut target)) = pin.targets.remove_entry(&target_id) {
621 while let Some(event) = target.poll(cx, now) {
622 match event {
623 TargetEvent::Request(req) => {
624 let _ = pin.submit_internal_command(
625 target.target_id().clone(),
626 req,
627 now,
628 );
629 }
630 TargetEvent::Command(msg) => {
631 pin.on_target_message(&mut target, msg, now);
632 }
633 TargetEvent::NavigationRequest(id, req) => {
634 pin.submit_navigation(id, req, now);
635 }
636 TargetEvent::NavigationResult(res) => {
637 pin.on_navigation_lifecycle_completed(res)
638 }
639 }
640 }
641
642 target.event_listeners_mut().poll(cx);
644 pin.event_listeners_mut().poll(cx);
646
647 pin.targets.insert(id, target);
648 pin.target_ids.push(target_id);
649 }
650 }
651
652 let mut done = true;
653
654 while let Poll::Ready(Some(ev)) = Pin::new(&mut pin.conn).poll_next(cx) {
655 match ev {
656 Ok(Message::Response(resp)) => {
657 pin.on_response(resp);
658 if pin.closing {
659 return Poll::Ready(None);
661 }
662 }
663 Ok(Message::Event(ev)) => {
664 pin.on_event(ev);
665 }
666 Err(err) => {
667 tracing::error!("WS Connection error: {:?}", err);
668 return Poll::Ready(Some(Err(err)));
669 }
670 }
671 done = false;
672 }
673
674 if pin.evict_command_timeout.poll_ready(cx) {
675 pin.evict_timed_out_commands(now);
677 }
678
679 if done {
680 return Poll::Pending;
682 }
683 }
684 }
685}
686
687#[derive(Debug, Clone)]
689pub struct HandlerConfig {
690 pub ignore_https_errors: bool,
692 pub viewport: Option<Viewport>,
694 pub context_ids: Vec<BrowserContextId>,
696 pub request_timeout: Duration,
698 pub request_intercept: bool,
700 pub cache_enabled: bool,
702 pub service_worker_enabled: bool,
704 pub ignore_visuals: bool,
706 pub ignore_stylesheets: bool,
708 pub ignore_javascript: bool,
710 pub ignore_analytics: bool,
712 pub ignore_ads: bool,
714 pub extra_headers: Option<std::collections::HashMap<String, String>>,
716 pub only_html: bool,
718 pub created_first_target: bool,
720 pub intercept_manager: NetworkInterceptManager,
722}
723
724impl Default for HandlerConfig {
725 fn default() -> Self {
726 Self {
727 ignore_https_errors: true,
728 viewport: Default::default(),
729 context_ids: Vec::new(),
730 request_timeout: Duration::from_millis(REQUEST_TIMEOUT),
731 request_intercept: false,
732 cache_enabled: true,
733 service_worker_enabled: true,
734 ignore_visuals: false,
735 ignore_stylesheets: false,
736 ignore_ads: false,
737 ignore_javascript: false,
738 ignore_analytics: true,
739 only_html: false,
740 extra_headers: Default::default(),
741 created_first_target: false,
742 intercept_manager: NetworkInterceptManager::UNKNOWN,
743 }
744 }
745}
746
747#[derive(Debug)]
749pub struct NavigationInProgress<T> {
750 navigated: bool,
752 response: Option<Response>,
754 tx: OneshotSender<T>,
756}
757
758impl<T> NavigationInProgress<T> {
759 fn new(tx: OneshotSender<T>) -> Self {
760 Self {
761 navigated: false,
762 response: None,
763 tx,
764 }
765 }
766
767 fn set_response(&mut self, resp: Response) {
769 self.response = Some(resp);
770 }
771
772 fn set_navigated(&mut self) {
774 self.navigated = true;
775 }
776}
777
778#[derive(Debug)]
780enum NavigationRequest {
781 Navigate(NavigationInProgress<Result<Response>>),
783 }
785
786#[derive(Debug)]
789enum PendingRequest {
790 CreateTarget(OneshotSender<Result<Page>>),
793 GetTargets(OneshotSender<Result<Vec<TargetInfo>>>),
795 Navigate(NavigationId),
802 ExternalCommand(OneshotSender<Result<Response>>),
804 InternalCommand(TargetId),
807 CloseBrowser(OneshotSender<Result<CloseReturns>>),
809}
810
811#[derive(Debug)]
815pub(crate) enum HandlerMessage {
816 CreatePage(CreateTargetParams, OneshotSender<Result<Page>>),
817 FetchTargets(OneshotSender<Result<Vec<TargetInfo>>>),
818 InsertContext(BrowserContext),
819 DisposeContext(BrowserContext),
820 GetPages(OneshotSender<Vec<Page>>),
821 Command(CommandMessage),
822 GetPage(TargetId, OneshotSender<Option<Page>>),
823 AddEventListener(EventListenerRequest),
824 CloseBrowser(OneshotSender<Result<CloseReturns>>),
825}