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 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 },
473 browser_ctx,
474 );
475
476 self.target_ids.push(target.target_id().clone());
477 self.targets.insert(target.target_id().clone(), target);
478 }
479
480 fn on_attached_to_target(&mut self, event: Box<EventAttachedToTarget>) {
482 let session = Session::new(event.session_id.clone(), event.target_info.target_id);
483 if let Some(target) = self.targets.get_mut(session.target_id()) {
484 target.set_session_id(session.session_id().clone())
485 }
486 self.sessions.insert(event.session_id, session);
487 }
488
489 fn on_detached_from_target(&mut self, event: EventDetachedFromTarget) {
493 if let Some(session) = self.sessions.remove(&event.session_id) {
495 if let Some(target) = self.targets.get_mut(session.target_id()) {
496 target.session_id().take();
497 }
498 }
499 }
500
501 fn on_target_destroyed(&mut self, event: EventTargetDestroyed) {
503 if let Some(target) = self.targets.remove(&event.target_id) {
504 if let Some(session) = target.session_id() {
506 self.sessions.remove(session);
507 }
508 }
509 }
510
511 fn evict_timed_out_commands(&mut self, now: Instant) {
516 let timed_out = self
517 .pending_commands
518 .iter()
519 .filter(|(_, (_, _, timestamp))| now > (*timestamp + self.config.request_timeout))
520 .map(|(k, _)| *k)
521 .collect::<Vec<_>>();
522
523 for call in timed_out {
524 if let Some((req, _, _)) = self.pending_commands.remove(&call) {
525 match req {
526 PendingRequest::CreateTarget(tx) => {
527 let _ = tx.send(Err(CdpError::Timeout));
528 }
529 PendingRequest::GetTargets(tx) => {
530 let _ = tx.send(Err(CdpError::Timeout));
531 }
532 PendingRequest::Navigate(nav) => {
533 if let Some(nav) = self.navigations.remove(&nav) {
534 match nav {
535 NavigationRequest::Navigate(nav) => {
536 let _ = nav.tx.send(Err(CdpError::Timeout));
537 }
538 }
539 }
540 }
541 PendingRequest::ExternalCommand(tx) => {
542 let _ = tx.send(Err(CdpError::Timeout));
543 }
544 PendingRequest::InternalCommand(_) => {}
545 PendingRequest::CloseBrowser(tx) => {
546 let _ = tx.send(Err(CdpError::Timeout));
547 }
548 }
549 }
550 }
551 }
552
553 pub fn event_listeners_mut(&mut self) -> &mut EventListeners {
554 &mut self.event_listeners
555 }
556}
557
558impl Stream for Handler {
559 type Item = Result<()>;
560
561 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
562 let pin = self.get_mut();
563
564 loop {
565 let now = Instant::now();
566 while let Poll::Ready(Some(msg)) = Pin::new(&mut pin.from_browser).poll_next(cx) {
570 match msg {
571 HandlerMessage::Command(cmd) => {
572 pin.submit_external_command(cmd, now)?;
573 }
574 HandlerMessage::FetchTargets(tx) => {
575 pin.submit_fetch_targets(tx, now);
576 }
577 HandlerMessage::CloseBrowser(tx) => {
578 pin.submit_close(tx, now);
579 }
580 HandlerMessage::CreatePage(params, tx) => {
581 pin.create_page(params, tx);
582 }
583 HandlerMessage::GetPages(tx) => {
584 let pages: Vec<_> = pin
585 .targets
586 .values_mut()
587 .filter(|p: &&mut Target| p.is_page())
588 .filter_map(|target| target.get_or_create_page())
589 .map(|page| Page::from(page.clone()))
590 .collect();
591 let _ = tx.send(pages);
592 }
593 HandlerMessage::InsertContext(ctx) => {
594 if pin.default_browser_context.id.is_none() {
595 pin.default_browser_context = ctx.clone();
596 }
597 pin.browser_contexts.insert(ctx);
598 }
599 HandlerMessage::DisposeContext(ctx) => {
600 pin.browser_contexts.remove(&ctx);
601 }
602 HandlerMessage::GetPage(target_id, tx) => {
603 let page = pin
604 .targets
605 .get_mut(&target_id)
606 .and_then(|target| target.get_or_create_page())
607 .map(|page| Page::from(page.clone()));
608 let _ = tx.send(page);
609 }
610 HandlerMessage::AddEventListener(req) => {
611 pin.event_listeners.add_listener(req);
612 }
613 }
614 }
615
616 for n in (0..pin.target_ids.len()).rev() {
617 let target_id = pin.target_ids.swap_remove(n);
618
619 if let Some((id, mut target)) = pin.targets.remove_entry(&target_id) {
620 while let Some(event) = target.poll(cx, now) {
621 match event {
622 TargetEvent::Request(req) => {
623 let _ = pin.submit_internal_command(
624 target.target_id().clone(),
625 req,
626 now,
627 );
628 }
629 TargetEvent::Command(msg) => {
630 pin.on_target_message(&mut target, msg, now);
631 }
632 TargetEvent::NavigationRequest(id, req) => {
633 pin.submit_navigation(id, req, now);
634 }
635 TargetEvent::NavigationResult(res) => {
636 pin.on_navigation_lifecycle_completed(res)
637 }
638 }
639 }
640
641 target.event_listeners_mut().poll(cx);
643 pin.event_listeners_mut().poll(cx);
645
646 pin.targets.insert(id, target);
647 pin.target_ids.push(target_id);
648 }
649 }
650
651 let mut done = true;
652
653 while let Poll::Ready(Some(ev)) = Pin::new(&mut pin.conn).poll_next(cx) {
654 match ev {
655 Ok(Message::Response(resp)) => {
656 pin.on_response(resp);
657 if pin.closing {
658 return Poll::Ready(None);
660 }
661 }
662 Ok(Message::Event(ev)) => {
663 pin.on_event(ev);
664 }
665 Err(err) => {
666 tracing::error!("WS Connection error: {:?}", err);
667 return Poll::Ready(Some(Err(err)));
668 }
669 }
670 done = false;
671 }
672
673 if pin.evict_command_timeout.poll_ready(cx) {
674 pin.evict_timed_out_commands(now);
676 }
677
678 if done {
679 return Poll::Pending;
681 }
682 }
683 }
684}
685
686#[derive(Debug, Clone)]
688pub struct HandlerConfig {
689 pub ignore_https_errors: bool,
691 pub viewport: Option<Viewport>,
693 pub context_ids: Vec<BrowserContextId>,
695 pub request_timeout: Duration,
697 pub request_intercept: bool,
699 pub cache_enabled: bool,
701 pub ignore_visuals: bool,
703 pub ignore_stylesheets: bool,
705 pub ignore_javascript: bool,
707 pub ignore_analytics: bool,
709 pub ignore_ads: bool,
711 pub extra_headers: Option<std::collections::HashMap<String, String>>,
713 pub only_html: bool,
715 pub created_first_target: bool,
717 pub intercept_manager: NetworkInterceptManager,
719}
720
721impl Default for HandlerConfig {
722 fn default() -> Self {
723 Self {
724 ignore_https_errors: true,
725 viewport: Default::default(),
726 context_ids: Vec::new(),
727 request_timeout: Duration::from_millis(REQUEST_TIMEOUT),
728 request_intercept: false,
729 cache_enabled: true,
730 ignore_visuals: false,
731 ignore_stylesheets: false,
732 ignore_ads: false,
733 ignore_javascript: false,
734 ignore_analytics: true,
735 only_html: false,
736 extra_headers: Default::default(),
737 created_first_target: false,
738 intercept_manager: NetworkInterceptManager::UNKNOWN,
739 }
740 }
741}
742
743#[derive(Debug)]
745pub struct NavigationInProgress<T> {
746 navigated: bool,
748 response: Option<Response>,
750 tx: OneshotSender<T>,
752}
753
754impl<T> NavigationInProgress<T> {
755 fn new(tx: OneshotSender<T>) -> Self {
756 Self {
757 navigated: false,
758 response: None,
759 tx,
760 }
761 }
762
763 fn set_response(&mut self, resp: Response) {
765 self.response = Some(resp);
766 }
767
768 fn set_navigated(&mut self) {
770 self.navigated = true;
771 }
772}
773
774#[derive(Debug)]
776enum NavigationRequest {
777 Navigate(NavigationInProgress<Result<Response>>),
779 }
781
782#[derive(Debug)]
785enum PendingRequest {
786 CreateTarget(OneshotSender<Result<Page>>),
789 GetTargets(OneshotSender<Result<Vec<TargetInfo>>>),
791 Navigate(NavigationId),
798 ExternalCommand(OneshotSender<Result<Response>>),
800 InternalCommand(TargetId),
803 CloseBrowser(OneshotSender<Result<CloseReturns>>),
805}
806
807#[derive(Debug)]
811pub(crate) enum HandlerMessage {
812 CreatePage(CreateTargetParams, OneshotSender<Result<Page>>),
813 FetchTargets(OneshotSender<Result<Vec<TargetInfo>>>),
814 InsertContext(BrowserContext),
815 DisposeContext(BrowserContext),
816 GetPages(OneshotSender<Vec<Page>>),
817 Command(CommandMessage),
818 GetPage(TargetId, OneshotSender<Option<Page>>),
819 AddEventListener(EventListenerRequest),
820 CloseBrowser(OneshotSender<Result<CloseReturns>>),
821}