1use crate::listeners::{EventListenerRequest, EventListeners};
2use chromiumoxide_cdp::cdp::browser_protocol::browser::*;
3use chromiumoxide_cdp::cdp::browser_protocol::target::*;
4use chromiumoxide_cdp::cdp::events::CdpEvent;
5use chromiumoxide_cdp::cdp::events::CdpEventMessage;
6use chromiumoxide_types::{CallId, Message, Method, Response};
7use chromiumoxide_types::{MethodId, Request as CdpRequest};
8use fnv::FnvHashMap;
9use futures::channel::mpsc::Receiver;
10use futures::channel::oneshot::Sender as OneshotSender;
11use futures::stream::{Fuse, Stream, StreamExt};
12use futures::task::{Context, Poll};
13use hashbrown::{HashMap, HashSet};
14use spider_network_blocker::intercept_manager::NetworkInterceptManager;
15use std::pin::Pin;
16use std::time::{Duration, Instant};
17use tokio_tungstenite::tungstenite::error::ProtocolError;
18use tokio_tungstenite::tungstenite::Error;
19
20use crate::cmd::{to_command_response, CommandMessage};
21use crate::conn::Connection;
22use crate::error::{CdpError, Result};
23use crate::handler::browser::BrowserContext;
24use crate::handler::frame::FrameRequestedNavigation;
25use crate::handler::frame::{NavigationError, NavigationId, NavigationOk};
26use crate::handler::job::PeriodicJob;
27use crate::handler::session::Session;
28use crate::handler::target::TargetEvent;
29use crate::handler::target::{Target, TargetConfig};
30use crate::handler::viewport::Viewport;
31use crate::page::Page;
32pub(crate) use page::PageInner;
33
34pub const REQUEST_TIMEOUT: u64 = 30_000;
36
37pub mod blockers;
38pub mod browser;
39pub mod commandfuture;
40pub mod domworld;
41pub mod emulation;
42pub mod frame;
43pub mod http;
44pub mod httpfuture;
45mod job;
46pub mod network;
47pub mod network_utils;
48mod page;
49mod session;
50pub mod target;
51pub mod target_message_future;
52pub mod viewport;
53
54#[must_use = "streams do nothing unless polled"]
57#[derive(Debug)]
58pub struct Handler {
59 pub default_browser_context: BrowserContext,
60 pub browser_contexts: HashSet<BrowserContext>,
61 pending_commands: FnvHashMap<CallId, (PendingRequest, MethodId, Instant)>,
65 from_browser: Fuse<Receiver<HandlerMessage>>,
67 target_ids: Vec<TargetId>,
69 targets: HashMap<TargetId, Target>,
71 navigations: FnvHashMap<NavigationId, NavigationRequest>,
73 sessions: HashMap<SessionId, Session>,
77 conn: Connection<CdpEventMessage>,
79 evict_command_timeout: PeriodicJob,
81 next_navigation_id: usize,
83 config: HandlerConfig,
85 event_listeners: EventListeners,
87 closing: bool,
89 remaining_bytes: Option<u64>,
91 budget_exhausted: bool,
93 attached_targets: HashSet<TargetId>,
95}
96
97lazy_static::lazy_static! {
98 static ref DISCOVER_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
100 let discover = SetDiscoverTargetsParams::new(true);
101 (discover.identifier(), serde_json::to_value(discover).expect("valid discover target params"))
102 };
103 static ref TARGET_PARAMS_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
105 let msg = GetTargetsParams { filter: None };
106 (msg.identifier(), serde_json::to_value(msg).expect("valid paramtarget"))
107 };
108 static ref CLOSE_PARAMS_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
110 let close_msg = CloseParams::default();
111 (close_msg.identifier(), serde_json::to_value(close_msg).expect("valid close params"))
112 };
113}
114
115fn maybe_store_attach_session_id(target: &mut Target, method: &MethodId, resp: &Response) {
116 if method.as_ref() != AttachToTargetParams::IDENTIFIER {
117 return;
118 }
119
120 if let Ok(resp) = to_command_response::<AttachToTargetParams>(resp.clone(), method.clone()) {
121 target.set_session_id(resp.result.session_id);
122 }
123}
124
125impl Handler {
126 pub(crate) fn new(
129 mut conn: Connection<CdpEventMessage>,
130 rx: Receiver<HandlerMessage>,
131 config: HandlerConfig,
132 ) -> Self {
133 let discover = DISCOVER_ID.clone();
134 let _ = conn.submit_command(discover.0, None, discover.1);
135
136 let browser_contexts = config
137 .context_ids
138 .iter()
139 .map(|id| BrowserContext::from(id.clone()))
140 .collect();
141
142 Self {
143 pending_commands: Default::default(),
144 from_browser: rx.fuse(),
145 default_browser_context: Default::default(),
146 browser_contexts,
147 target_ids: Default::default(),
148 targets: Default::default(),
149 navigations: Default::default(),
150 sessions: Default::default(),
151 conn,
152 evict_command_timeout: PeriodicJob::new(config.request_timeout),
153 next_navigation_id: 0,
154 config,
155 event_listeners: Default::default(),
156 closing: false,
157 remaining_bytes: None,
158 budget_exhausted: false,
159 attached_targets: Default::default(),
160 }
161 }
162
163 pub fn get_target(&self, target_id: &TargetId) -> Option<&Target> {
165 self.targets.get(target_id)
166 }
167
168 pub fn targets(&self) -> impl Iterator<Item = &Target> + '_ {
170 self.targets.values()
171 }
172
173 pub fn default_browser_context(&self) -> &BrowserContext {
175 &self.default_browser_context
176 }
177
178 pub fn browser_contexts(&self) -> impl Iterator<Item = &BrowserContext> + '_ {
180 self.browser_contexts.iter()
181 }
182
183 fn on_navigation_response(&mut self, id: NavigationId, resp: Response) {
185 if let Some(nav) = self.navigations.remove(&id) {
186 match nav {
187 NavigationRequest::Navigate(mut nav) => {
188 if nav.navigated {
189 let _ = nav.tx.send(Ok(resp));
190 } else {
191 nav.set_response(resp);
192 self.navigations
193 .insert(id, NavigationRequest::Navigate(nav));
194 }
195 }
196 }
197 }
198 }
199
200 fn on_navigation_lifecycle_completed(&mut self, res: Result<NavigationOk, NavigationError>) {
202 match res {
203 Ok(ok) => {
204 let id = *ok.navigation_id();
205 if let Some(nav) = self.navigations.remove(&id) {
206 match nav {
207 NavigationRequest::Navigate(mut nav) => {
208 if let Some(resp) = nav.response.take() {
209 let _ = nav.tx.send(Ok(resp));
210 } else {
211 nav.set_navigated();
212 self.navigations
213 .insert(id, NavigationRequest::Navigate(nav));
214 }
215 }
216 }
217 }
218 }
219 Err(err) => {
220 if let Some(nav) = self.navigations.remove(err.navigation_id()) {
221 match nav {
222 NavigationRequest::Navigate(nav) => {
223 let _ = nav.tx.send(Err(err.into()));
224 }
225 }
226 }
227 }
228 }
229 }
230
231 fn on_response(&mut self, resp: Response) {
233 if let Some((req, method, _)) = self.pending_commands.remove(&resp.id) {
234 match req {
235 PendingRequest::CreateTarget(tx) => {
236 match to_command_response::<CreateTargetParams>(resp, method) {
237 Ok(resp) => {
238 if let Some(target) = self.targets.get_mut(&resp.target_id) {
239 target.set_initiator(tx);
240 } else {
241 let _ = tx.send(Err(CdpError::NotFound)).ok();
242 }
243 }
244 Err(err) => {
245 let _ = tx.send(Err(err)).ok();
246 }
247 }
248 }
249 PendingRequest::GetTargets(tx) => {
250 match to_command_response::<GetTargetsParams>(resp, method) {
251 Ok(resp) => {
252 let targets = resp.result.target_infos;
253 let results = targets.clone();
254
255 for target_info in targets {
256 let event: EventTargetCreated = EventTargetCreated { target_info };
257 self.on_target_created(event);
258 }
259
260 let _ = tx.send(Ok(results)).ok();
261 }
262 Err(err) => {
263 let _ = tx.send(Err(err)).ok();
264 }
265 }
266 }
267 PendingRequest::Navigate(id) => {
268 self.on_navigation_response(id, resp);
269 if self.config.only_html && !self.config.created_first_target {
270 self.config.created_first_target = true;
271 }
272 }
273 PendingRequest::ExternalCommand(tx) => {
274 let _ = tx.send(Ok(resp)).ok();
275 }
276 PendingRequest::InternalCommand(target_id) => {
277 if let Some(target) = self.targets.get_mut(&target_id) {
278 maybe_store_attach_session_id(target, &method, &resp);
279 target.on_response(resp, method.as_ref());
280 }
281 }
282 PendingRequest::CloseBrowser(tx) => {
283 self.closing = true;
284 let _ = tx.send(Ok(CloseReturns {})).ok();
285 }
286 }
287 }
288 }
289
290 pub(crate) fn submit_external_command(
292 &mut self,
293 msg: CommandMessage,
294 now: Instant,
295 ) -> Result<()> {
296 let call_id = self
297 .conn
298 .submit_command(msg.method.clone(), msg.session_id, msg.params)?;
299 self.pending_commands.insert(
300 call_id,
301 (PendingRequest::ExternalCommand(msg.sender), msg.method, now),
302 );
303 Ok(())
304 }
305
306 pub(crate) fn submit_internal_command(
307 &mut self,
308 target_id: TargetId,
309 req: CdpRequest,
310 now: Instant,
311 ) -> Result<()> {
312 let call_id = self.conn.submit_command(
313 req.method.clone(),
314 req.session_id.map(Into::into),
315 req.params,
316 )?;
317 self.pending_commands.insert(
318 call_id,
319 (PendingRequest::InternalCommand(target_id), req.method, now),
320 );
321 Ok(())
322 }
323
324 fn submit_fetch_targets(&mut self, tx: OneshotSender<Result<Vec<TargetInfo>>>, now: Instant) {
325 let msg = TARGET_PARAMS_ID.clone();
326
327 if let Ok(call_id) = self.conn.submit_command(msg.0.clone(), None, msg.1) {
328 self.pending_commands
329 .insert(call_id, (PendingRequest::GetTargets(tx), msg.0, now));
330 }
331 }
332
333 fn submit_navigation(&mut self, id: NavigationId, req: CdpRequest, now: Instant) {
336 if let Ok(call_id) = self.conn.submit_command(
337 req.method.clone(),
338 req.session_id.map(Into::into),
339 req.params,
340 ) {
341 self.pending_commands
342 .insert(call_id, (PendingRequest::Navigate(id), req.method, now));
343 }
344 }
345
346 fn submit_close(&mut self, tx: OneshotSender<Result<CloseReturns>>, now: Instant) {
347 let close_msg = CLOSE_PARAMS_ID.clone();
348
349 if let Ok(call_id) = self
350 .conn
351 .submit_command(close_msg.0.clone(), None, close_msg.1)
352 {
353 self.pending_commands.insert(
354 call_id,
355 (PendingRequest::CloseBrowser(tx), close_msg.0, now),
356 );
357 }
358 }
359
360 fn on_target_message(&mut self, target: &mut Target, msg: CommandMessage, now: Instant) {
362 if msg.is_navigation() {
363 let (req, tx) = msg.split();
364 let id = self.next_navigation_id();
365
366 target.goto(FrameRequestedNavigation::new(
367 id,
368 req,
369 self.config.request_timeout,
370 ));
371
372 self.navigations.insert(
373 id,
374 NavigationRequest::Navigate(NavigationInProgress::new(tx)),
375 );
376 } else {
377 let _ = self.submit_external_command(msg, now);
378 }
379 }
380
381 fn next_navigation_id(&mut self) -> NavigationId {
383 let id = NavigationId(self.next_navigation_id);
384 self.next_navigation_id = self.next_navigation_id.wrapping_add(1);
385 id
386 }
387
388 fn create_page(&mut self, params: CreateTargetParams, tx: OneshotSender<Result<Page>>) {
399 let about_blank = params.url == "about:blank";
400 let http_check =
401 !about_blank && params.url.starts_with("http") || params.url.starts_with("file://");
402
403 if about_blank || http_check {
404 let method = params.identifier();
405
406 match serde_json::to_value(params) {
407 Ok(params) => match self.conn.submit_command(method.clone(), None, params) {
408 Ok(call_id) => {
409 self.pending_commands.insert(
410 call_id,
411 (PendingRequest::CreateTarget(tx), method, Instant::now()),
412 );
413 }
414 Err(err) => {
415 let _ = tx.send(Err(err.into())).ok();
416 }
417 },
418 Err(err) => {
419 let _ = tx.send(Err(err.into())).ok();
420 }
421 }
422 } else {
423 let _ = tx.send(Err(CdpError::NotFound)).ok();
424 }
425 }
426
427 fn on_event(&mut self, event: CdpEventMessage) {
429 if let Some(session_id) = &event.session_id {
430 if let Some(session) = self.sessions.get(session_id.as_str()) {
431 if let Some(target) = self.targets.get_mut(session.target_id()) {
432 return target.on_event(event);
433 }
434 }
435 }
436 let CdpEventMessage { params, method, .. } = event;
437
438 match params {
439 CdpEvent::TargetTargetCreated(ref ev) => self.on_target_created(*ev.clone()),
440 CdpEvent::TargetAttachedToTarget(ref ev) => self.on_attached_to_target(ev.clone()),
441 CdpEvent::TargetTargetDestroyed(ref ev) => self.on_target_destroyed(ev.clone()),
442 CdpEvent::TargetDetachedFromTarget(ref ev) => self.on_detached_from_target(ev.clone()),
443 _ => {}
444 }
445
446 chromiumoxide_cdp::consume_event!(match params {
447 |ev| self.event_listeners.start_send(ev),
448 |json| { let _ = self.event_listeners.try_send_custom(&method, json);}
449 });
450 }
451
452 fn on_target_created(&mut self, event: EventTargetCreated) {
456 if !self.browser_contexts.is_empty() {
457 if let Some(ref context_id) = event.target_info.browser_context_id {
458 let bc = BrowserContext {
459 id: Some(context_id.clone()),
460 };
461 if !self.browser_contexts.contains(&bc) {
462 return;
463 }
464 }
465 }
466 let browser_ctx = event
467 .target_info
468 .browser_context_id
469 .clone()
470 .map(BrowserContext::from)
471 .unwrap_or_else(|| self.default_browser_context.clone());
472 let target = Target::new(
473 event.target_info,
474 TargetConfig {
475 ignore_https_errors: self.config.ignore_https_errors,
476 request_timeout: self.config.request_timeout,
477 viewport: self.config.viewport.clone(),
478 request_intercept: self.config.request_intercept,
479 cache_enabled: self.config.cache_enabled,
480 service_worker_enabled: self.config.service_worker_enabled,
481 ignore_visuals: self.config.ignore_visuals,
482 ignore_stylesheets: self.config.ignore_stylesheets,
483 ignore_javascript: self.config.ignore_javascript,
484 ignore_analytics: self.config.ignore_analytics,
485 ignore_prefetch: self.config.ignore_prefetch,
486 extra_headers: self.config.extra_headers.clone(),
487 only_html: self.config.only_html && self.config.created_first_target,
488 intercept_manager: self.config.intercept_manager,
489 max_bytes_allowed: self.config.max_bytes_allowed,
490 whitelist_patterns: self.config.whitelist_patterns.clone(),
491 blacklist_patterns: self.config.blacklist_patterns.clone(),
492 },
493 browser_ctx,
494 );
495
496 self.target_ids.push(target.target_id().clone());
497 self.targets.insert(target.target_id().clone(), target);
498 }
499
500 fn on_attached_to_target(&mut self, event: Box<EventAttachedToTarget>) {
502 let session = Session::new(event.session_id.clone(), event.target_info.target_id);
503 if let Some(target) = self.targets.get_mut(session.target_id()) {
504 target.set_session_id(session.session_id().clone())
505 }
506 self.sessions.insert(event.session_id, session);
507 }
508
509 fn on_detached_from_target(&mut self, event: EventDetachedFromTarget) {
513 if let Some(session) = self.sessions.remove(&event.session_id) {
515 if let Some(target) = self.targets.get_mut(session.target_id()) {
516 target.session_id_mut().take();
517 }
518 }
519 }
520
521 fn on_target_destroyed(&mut self, event: EventTargetDestroyed) {
523 self.attached_targets.remove(&event.target_id);
524
525 if let Some(target) = self.targets.remove(&event.target_id) {
526 if let Some(session) = target.session_id() {
528 self.sessions.remove(session);
529 }
530 }
531 }
532
533 fn evict_timed_out_commands(&mut self, now: Instant) {
538 let deadline = match now.checked_sub(self.config.request_timeout) {
539 Some(d) => d,
540 None => return,
541 };
542
543 let timed_out: Vec<_> = self
544 .pending_commands
545 .iter()
546 .filter(|(_, (_, _, timestamp))| *timestamp < deadline)
547 .map(|(k, _)| *k)
548 .collect();
549
550 for call in timed_out {
551 if let Some((req, _, _)) = self.pending_commands.remove(&call) {
552 match req {
553 PendingRequest::CreateTarget(tx) => {
554 let _ = tx.send(Err(CdpError::Timeout));
555 }
556 PendingRequest::GetTargets(tx) => {
557 let _ = tx.send(Err(CdpError::Timeout));
558 }
559 PendingRequest::Navigate(nav) => {
560 if let Some(nav) = self.navigations.remove(&nav) {
561 match nav {
562 NavigationRequest::Navigate(nav) => {
563 let _ = nav.tx.send(Err(CdpError::Timeout));
564 }
565 }
566 }
567 }
568 PendingRequest::ExternalCommand(tx) => {
569 let _ = tx.send(Err(CdpError::Timeout));
570 }
571 PendingRequest::InternalCommand(_) => {}
572 PendingRequest::CloseBrowser(tx) => {
573 let _ = tx.send(Err(CdpError::Timeout));
574 }
575 }
576 }
577 }
578 }
579
580 pub fn event_listeners_mut(&mut self) -> &mut EventListeners {
581 &mut self.event_listeners
582 }
583}
584
585impl Stream for Handler {
586 type Item = Result<()>;
587
588 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
589 let pin = self.get_mut();
590
591 let mut dispose = false;
592
593 loop {
594 let now = Instant::now();
595 while let Poll::Ready(Some(msg)) = Pin::new(&mut pin.from_browser).poll_next(cx) {
599 match msg {
600 HandlerMessage::Command(cmd) => {
601 pin.submit_external_command(cmd, now)?;
602 }
603 HandlerMessage::FetchTargets(tx) => {
604 pin.submit_fetch_targets(tx, now);
605 }
606 HandlerMessage::CloseBrowser(tx) => {
607 pin.submit_close(tx, now);
608 }
609 HandlerMessage::CreatePage(params, tx) => {
610 if let Some(ref id) = params.browser_context_id {
611 pin.browser_contexts
612 .insert(BrowserContext::from(id.clone()));
613 }
614 pin.create_page(params, tx);
615 }
616 HandlerMessage::GetPages(tx) => {
617 let pages: Vec<_> = pin
618 .targets
619 .values_mut()
620 .filter(|p: &&mut Target| p.is_page())
621 .filter_map(|target| target.get_or_create_page())
622 .map(|page| Page::from(page.clone()))
623 .collect();
624 let _ = tx.send(pages);
625 }
626 HandlerMessage::InsertContext(ctx) => {
627 if pin.default_browser_context.id().is_none() {
628 pin.default_browser_context = ctx.clone();
629 }
630 pin.browser_contexts.insert(ctx);
631 }
632 HandlerMessage::DisposeContext(ctx) => {
633 pin.browser_contexts.remove(&ctx);
634 pin.attached_targets.retain(|tid| {
635 pin.targets
636 .get(tid)
637 .and_then(|t| t.browser_context_id()) .map(|id| Some(id) != ctx.id())
639 .unwrap_or(true)
640 });
641 pin.closing = true;
642 dispose = true;
643 }
644 HandlerMessage::GetPage(target_id, tx) => {
645 let page = pin
646 .targets
647 .get_mut(&target_id)
648 .and_then(|target| target.get_or_create_page())
649 .map(|page| Page::from(page.clone()));
650 let _ = tx.send(page);
651 }
652 HandlerMessage::AddEventListener(req) => {
653 pin.event_listeners.add_listener(req);
654 }
655 }
656 }
657
658 for n in (0..pin.target_ids.len()).rev() {
659 let target_id = pin.target_ids.swap_remove(n);
660
661 if let Some((id, mut target)) = pin.targets.remove_entry(&target_id) {
662 while let Some(event) = target.poll(cx, now) {
663 match event {
664 TargetEvent::Request(req) => {
665 let _ = pin.submit_internal_command(
666 target.target_id().clone(),
667 req,
668 now,
669 );
670 }
671 TargetEvent::Command(msg) => {
672 pin.on_target_message(&mut target, msg, now);
673 }
674 TargetEvent::NavigationRequest(id, req) => {
675 pin.submit_navigation(id, req, now);
676 }
677 TargetEvent::NavigationResult(res) => {
678 pin.on_navigation_lifecycle_completed(res)
679 }
680 TargetEvent::BytesConsumed(n) => {
681 if let Some(rem) = pin.remaining_bytes.as_mut() {
682 *rem = rem.saturating_sub(n);
683 if *rem == 0 {
684 pin.budget_exhausted = true;
685 }
686 }
687 }
688 }
689 }
690
691 target.event_listeners_mut().poll(cx);
693 pin.event_listeners_mut().poll(cx);
695
696 pin.targets.insert(id, target);
697 pin.target_ids.push(target_id);
698 }
699 }
700
701 let mut done = true;
702
703 while let Poll::Ready(Some(ev)) = Pin::new(&mut pin.conn).poll_next(cx) {
704 match ev {
705 Ok(boxed_msg) => match *boxed_msg {
706 Message::Response(resp) => {
707 pin.on_response(resp);
708 if pin.closing {
709 return Poll::Ready(None);
710 }
711 }
712 Message::Event(ev) => {
713 pin.on_event(ev);
714 }
715 },
716 Err(err) => {
717 tracing::error!("WS Connection error: {:?}", err);
718 if let CdpError::Ws(ref ws_error) = err {
719 match ws_error {
720 Error::AlreadyClosed => {
721 pin.closing = true;
722 dispose = true;
723 break;
724 }
725 Error::Protocol(detail)
726 if detail == &ProtocolError::ResetWithoutClosingHandshake =>
727 {
728 pin.closing = true;
729 dispose = true;
730 break;
731 }
732 _ => {}
733 }
734 }
735 return Poll::Ready(Some(Err(err)));
736 }
737 }
738 done = false;
739 }
740
741 if pin.evict_command_timeout.poll_ready(cx) {
742 pin.evict_timed_out_commands(now);
744 }
745
746 if pin.budget_exhausted {
747 for t in pin.targets.values_mut() {
748 t.network_manager.set_block_all(true);
749 }
750 }
751
752 if dispose {
753 return Poll::Ready(None);
754 }
755
756 if done {
757 return Poll::Pending;
759 }
760 }
761 }
762}
763
764#[derive(Debug, Clone)]
766pub struct HandlerConfig {
767 pub ignore_https_errors: bool,
769 pub viewport: Option<Viewport>,
771 pub context_ids: Vec<BrowserContextId>,
773 pub request_timeout: Duration,
775 pub request_intercept: bool,
777 pub cache_enabled: bool,
779 pub service_worker_enabled: bool,
781 pub ignore_visuals: bool,
783 pub ignore_stylesheets: bool,
785 pub ignore_javascript: bool,
787 pub ignore_analytics: bool,
789 pub ignore_prefetch: bool,
791 pub ignore_ads: bool,
793 pub extra_headers: Option<std::collections::HashMap<String, String>>,
795 pub only_html: bool,
797 pub created_first_target: bool,
799 pub intercept_manager: NetworkInterceptManager,
801 pub max_bytes_allowed: Option<u64>,
803 pub whitelist_patterns: Option<Vec<String>>,
805 pub blacklist_patterns: Option<Vec<String>>,
807 pub channel_capacity: usize,
810 pub connection_retries: u32,
813}
814
815impl Default for HandlerConfig {
816 fn default() -> Self {
817 Self {
818 ignore_https_errors: true,
819 viewport: Default::default(),
820 context_ids: Vec::new(),
821 request_timeout: Duration::from_millis(REQUEST_TIMEOUT),
822 request_intercept: false,
823 cache_enabled: true,
824 service_worker_enabled: true,
825 ignore_visuals: false,
826 ignore_stylesheets: false,
827 ignore_ads: false,
828 ignore_javascript: false,
829 ignore_analytics: true,
830 ignore_prefetch: true,
831 only_html: false,
832 extra_headers: Default::default(),
833 created_first_target: false,
834 intercept_manager: NetworkInterceptManager::Unknown,
835 max_bytes_allowed: None,
836 whitelist_patterns: None,
837 blacklist_patterns: None,
838 channel_capacity: 1000,
839 connection_retries: crate::conn::DEFAULT_CONNECTION_RETRIES,
840 }
841 }
842}
843
844#[derive(Debug)]
846pub struct NavigationInProgress<T> {
847 navigated: bool,
849 response: Option<Response>,
851 tx: OneshotSender<T>,
853}
854
855impl<T> NavigationInProgress<T> {
856 fn new(tx: OneshotSender<T>) -> Self {
857 Self {
858 navigated: false,
859 response: None,
860 tx,
861 }
862 }
863
864 fn set_response(&mut self, resp: Response) {
866 self.response = Some(resp);
867 }
868
869 fn set_navigated(&mut self) {
871 self.navigated = true;
872 }
873}
874
875#[derive(Debug)]
877enum NavigationRequest {
878 Navigate(NavigationInProgress<Result<Response>>),
880 }
882
883#[derive(Debug)]
886enum PendingRequest {
887 CreateTarget(OneshotSender<Result<Page>>),
890 GetTargets(OneshotSender<Result<Vec<TargetInfo>>>),
892 Navigate(NavigationId),
899 ExternalCommand(OneshotSender<Result<Response>>),
901 InternalCommand(TargetId),
904 CloseBrowser(OneshotSender<Result<CloseReturns>>),
906}
907
908#[derive(Debug)]
912pub(crate) enum HandlerMessage {
913 CreatePage(CreateTargetParams, OneshotSender<Result<Page>>),
914 FetchTargets(OneshotSender<Result<Vec<TargetInfo>>>),
915 InsertContext(BrowserContext),
916 DisposeContext(BrowserContext),
917 GetPages(OneshotSender<Vec<Page>>),
918 Command(CommandMessage),
919 GetPage(TargetId, OneshotSender<Option<Page>>),
920 AddEventListener(EventListenerRequest),
921 CloseBrowser(OneshotSender<Result<CloseReturns>>),
922}
923
924#[cfg(test)]
925mod tests {
926 use super::*;
927 use chromiumoxide_cdp::cdp::browser_protocol::target::{AttachToTargetReturns, TargetInfo};
928
929 #[test]
930 fn attach_to_target_response_sets_session_id_before_event_arrives() {
931 let info = TargetInfo::builder()
932 .target_id("target-1".to_string())
933 .r#type("page")
934 .title("")
935 .url("about:blank")
936 .attached(false)
937 .can_access_opener(false)
938 .build()
939 .expect("target info");
940 let mut target = Target::new(info, TargetConfig::default(), BrowserContext::default());
941 let method: MethodId = AttachToTargetParams::IDENTIFIER.into();
942 let result =
943 serde_json::to_value(AttachToTargetReturns::new("session-1".to_string())).expect("attach result");
944 let resp = Response {
945 id: CallId::new(1),
946 result: Some(result),
947 error: None,
948 };
949
950 maybe_store_attach_session_id(&mut target, &method, &resp);
951
952 assert_eq!(
953 target.session_id().map(AsRef::as_ref),
954 Some("session-1"),
955 "attach response should seed the flat session id even before Target.attachedToTarget"
956 );
957 }
958}