1use crate::listeners::{EventListenerRequest, EventListeners};
2use chromiumoxide_cdp::cdp::browser_protocol::browser::*;
3use chromiumoxide_cdp::cdp::browser_protocol::target::*;
4use chromiumoxide_cdp::cdp::events::CdpEvent;
5use chromiumoxide_cdp::cdp::events::CdpEventMessage;
6use chromiumoxide_types::{CallId, Message, Method, Response};
7use chromiumoxide_types::{MethodId, Request as CdpRequest};
8use fnv::FnvHashMap;
9use futures::channel::mpsc::Receiver;
10use futures::channel::oneshot::Sender as OneshotSender;
11use futures::stream::{Fuse, Stream, StreamExt};
12use futures::task::{Context, Poll};
13use hashbrown::{HashMap, HashSet};
14pub(crate) use page::PageInner;
15use spider_network_blocker::intercept_manager::NetworkInterceptManager;
16use std::pin::Pin;
17use std::time::{Duration, Instant};
18use tokio_tungstenite::tungstenite::error::ProtocolError;
19use tokio_tungstenite::tungstenite::Error;
20
21use crate::cmd::{to_command_response, CommandMessage};
22use crate::conn::Connection;
23use crate::error::{CdpError, Result};
24use crate::handler::browser::BrowserContext;
25use crate::handler::frame::FrameRequestedNavigation;
26use crate::handler::frame::{NavigationError, NavigationId, NavigationOk};
27use crate::handler::job::PeriodicJob;
28use crate::handler::session::Session;
29use crate::handler::target::TargetEvent;
30use crate::handler::target::{Target, TargetConfig};
31use crate::handler::viewport::Viewport;
32use crate::page::Page;
33
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;
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 pub default_browser_context: BrowserContext,
59 pub browser_contexts: HashSet<BrowserContext>,
60 pending_commands: FnvHashMap<CallId, (PendingRequest, MethodId, Instant)>,
64 from_browser: Fuse<Receiver<HandlerMessage>>,
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 remaining_bytes: Option<u64>,
90 budget_exhausted: bool,
92 attached_targets: HashSet<TargetId>,
94}
95
96lazy_static::lazy_static! {
97 static ref DISCOVER_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
99 let discover = SetDiscoverTargetsParams::new(true);
100 (discover.identifier(), serde_json::to_value(discover).expect("valid discover target params"))
101 };
102 static ref TARGET_PARAMS_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
104 let msg = GetTargetsParams { filter: None };
105 (msg.identifier(), serde_json::to_value(msg).expect("valid paramtarget"))
106 };
107 static ref CLOSE_PARAMS_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
109 let close_msg = CloseParams::default();
110 (close_msg.identifier(), serde_json::to_value(close_msg).expect("valid close params"))
111 };
112}
113
114impl Handler {
115 pub(crate) fn new(
118 mut conn: Connection<CdpEventMessage>,
119 rx: Receiver<HandlerMessage>,
120 config: HandlerConfig,
121 ) -> Self {
122 let discover = DISCOVER_ID.clone();
123 let _ = conn.submit_command(discover.0, None, discover.1);
124
125 let browser_contexts = config
126 .context_ids
127 .iter()
128 .map(|id| BrowserContext::from(id.clone()))
129 .collect();
130
131 Self {
132 pending_commands: Default::default(),
133 from_browser: rx.fuse(),
134 default_browser_context: Default::default(),
135 browser_contexts,
136 target_ids: Default::default(),
137 targets: Default::default(),
138 navigations: Default::default(),
139 sessions: Default::default(),
140 conn,
141 evict_command_timeout: PeriodicJob::new(config.request_timeout),
142 next_navigation_id: 0,
143 config,
144 event_listeners: Default::default(),
145 closing: false,
146 remaining_bytes: None,
147 budget_exhausted: false,
148 attached_targets: Default::default(),
149 }
150 }
151
152 pub fn get_target(&self, target_id: &TargetId) -> Option<&Target> {
154 self.targets.get(target_id)
155 }
156
157 pub fn targets(&self) -> impl Iterator<Item = &Target> + '_ {
159 self.targets.values()
160 }
161
162 pub fn default_browser_context(&self) -> &BrowserContext {
164 &self.default_browser_context
165 }
166
167 pub fn browser_contexts(&self) -> impl Iterator<Item = &BrowserContext> + '_ {
169 self.browser_contexts.iter()
170 }
171
172 fn on_navigation_response(&mut self, id: NavigationId, resp: Response) {
174 if let Some(nav) = self.navigations.remove(&id) {
175 match nav {
176 NavigationRequest::Navigate(mut nav) => {
177 if nav.navigated {
178 let _ = nav.tx.send(Ok(resp));
179 } else {
180 nav.set_response(resp);
181 self.navigations
182 .insert(id, NavigationRequest::Navigate(nav));
183 }
184 }
185 }
186 }
187 }
188
189 fn on_navigation_lifecycle_completed(&mut self, res: Result<NavigationOk, NavigationError>) {
191 match res {
192 Ok(ok) => {
193 let id = *ok.navigation_id();
194 if let Some(nav) = self.navigations.remove(&id) {
195 match nav {
196 NavigationRequest::Navigate(mut nav) => {
197 if let Some(resp) = nav.response.take() {
198 let _ = nav.tx.send(Ok(resp));
199 } else {
200 nav.set_navigated();
201 self.navigations
202 .insert(id, NavigationRequest::Navigate(nav));
203 }
204 }
205 }
206 }
207 }
208 Err(err) => {
209 if let Some(nav) = self.navigations.remove(err.navigation_id()) {
210 match nav {
211 NavigationRequest::Navigate(nav) => {
212 let _ = nav.tx.send(Err(err.into()));
213 }
214 }
215 }
216 }
217 }
218 }
219
220 fn on_response(&mut self, resp: Response) {
222 if let Some((req, method, _)) = self.pending_commands.remove(&resp.id) {
223 match req {
224 PendingRequest::CreateTarget(tx) => {
225 match to_command_response::<CreateTargetParams>(resp, method) {
226 Ok(resp) => {
227 if let Some(target) = self.targets.get_mut(&resp.target_id) {
228 target.set_initiator(tx);
229 } else {
230 let _ = tx.send(Err(CdpError::NotFound)).ok();
231 }
232 }
233 Err(err) => {
234 let _ = tx.send(Err(err)).ok();
235 }
236 }
237 }
238 PendingRequest::GetTargets(tx) => {
239 match to_command_response::<GetTargetsParams>(resp, method) {
240 Ok(resp) => {
241 let targets = resp.result.target_infos;
242 let results = targets.clone();
243
244 for target_info in targets {
245 let event: EventTargetCreated = EventTargetCreated { target_info };
246 self.on_target_created(event);
247 }
248
249 let _ = tx.send(Ok(results)).ok();
250 }
251 Err(err) => {
252 let _ = tx.send(Err(err)).ok();
253 }
254 }
255 }
256 PendingRequest::Navigate(id) => {
257 self.on_navigation_response(id, resp);
258 if self.config.only_html && !self.config.created_first_target {
259 self.config.created_first_target = true;
260 }
261 }
262 PendingRequest::ExternalCommand(tx) => {
263 let _ = tx.send(Ok(resp)).ok();
264 }
265 PendingRequest::InternalCommand(target_id) => {
266 if let Some(target) = self.targets.get_mut(&target_id) {
267 target.on_response(resp, method.as_ref());
268 }
269 }
270 PendingRequest::CloseBrowser(tx) => {
271 self.closing = true;
272 let _ = tx.send(Ok(CloseReturns {})).ok();
273 }
274 }
275 }
276 }
277
278 pub(crate) fn submit_external_command(
280 &mut self,
281 msg: CommandMessage,
282 now: Instant,
283 ) -> Result<()> {
284 let call_id = self
285 .conn
286 .submit_command(msg.method.clone(), msg.session_id, msg.params)?;
287 self.pending_commands.insert(
288 call_id,
289 (PendingRequest::ExternalCommand(msg.sender), msg.method, now),
290 );
291 Ok(())
292 }
293
294 pub(crate) fn submit_internal_command(
295 &mut self,
296 target_id: TargetId,
297 req: CdpRequest,
298 now: Instant,
299 ) -> Result<()> {
300 let call_id = self.conn.submit_command(
301 req.method.clone(),
302 req.session_id.map(Into::into),
303 req.params,
304 )?;
305 self.pending_commands.insert(
306 call_id,
307 (PendingRequest::InternalCommand(target_id), req.method, now),
308 );
309 Ok(())
310 }
311
312 fn submit_fetch_targets(&mut self, tx: OneshotSender<Result<Vec<TargetInfo>>>, now: Instant) {
313 let msg = TARGET_PARAMS_ID.clone();
314
315 if let Ok(call_id) = self.conn.submit_command(msg.0.clone(), None, msg.1) {
316 self.pending_commands
317 .insert(call_id, (PendingRequest::GetTargets(tx), msg.0, now));
318 }
319 }
320
321 fn submit_navigation(&mut self, id: NavigationId, req: CdpRequest, now: Instant) {
324 if let Ok(call_id) = self.conn.submit_command(
325 req.method.clone(),
326 req.session_id.map(Into::into),
327 req.params,
328 ) {
329 self.pending_commands
330 .insert(call_id, (PendingRequest::Navigate(id), req.method, now));
331 }
332 }
333
334 fn submit_close(&mut self, tx: OneshotSender<Result<CloseReturns>>, now: Instant) {
335 let close_msg = CLOSE_PARAMS_ID.clone();
336
337 if let Ok(call_id) = self
338 .conn
339 .submit_command(close_msg.0.clone(), None, close_msg.1)
340 {
341 self.pending_commands.insert(
342 call_id,
343 (PendingRequest::CloseBrowser(tx), close_msg.0, now),
344 );
345 }
346 }
347
348 fn on_target_message(&mut self, target: &mut Target, msg: CommandMessage, now: Instant) {
350 if msg.is_navigation() {
351 let (req, tx) = msg.split();
352 let id = self.next_navigation_id();
353
354 target.goto(FrameRequestedNavigation::new(id, req));
355
356 self.navigations.insert(
357 id,
358 NavigationRequest::Navigate(NavigationInProgress::new(tx)),
359 );
360 } else {
361 let _ = self.submit_external_command(msg, now);
362 }
363 }
364
365 fn next_navigation_id(&mut self) -> NavigationId {
367 let id = NavigationId(self.next_navigation_id);
368 self.next_navigation_id = self.next_navigation_id.wrapping_add(1);
369 id
370 }
371
372 fn create_page(&mut self, params: CreateTargetParams, tx: OneshotSender<Result<Page>>) {
383 let about_blank = params.url == "about:blank";
384 let http_check =
385 !about_blank && params.url.starts_with("http") || params.url.starts_with("file://");
386
387 if about_blank || http_check {
388 let method = params.identifier();
389
390 match serde_json::to_value(params) {
391 Ok(params) => match self.conn.submit_command(method.clone(), None, params) {
392 Ok(call_id) => {
393 self.pending_commands.insert(
394 call_id,
395 (PendingRequest::CreateTarget(tx), method, Instant::now()),
396 );
397 }
398 Err(err) => {
399 let _ = tx.send(Err(err.into())).ok();
400 }
401 },
402 Err(err) => {
403 let _ = tx.send(Err(err.into())).ok();
404 }
405 }
406 } else {
407 let _ = tx.send(Err(CdpError::NotFound)).ok();
408 }
409 }
410
411 fn on_event(&mut self, event: CdpEventMessage) {
413 if let Some(ref session_id) = event.session_id {
414 if let Some(session) = self.sessions.get(session_id.as_str()) {
415 if let Some(target) = self.targets.get_mut(session.target_id()) {
416 return target.on_event(event);
417 }
418 }
419 }
420 let CdpEventMessage { params, method, .. } = event;
421
422 match params {
423 CdpEvent::TargetTargetCreated(ref ev) => self.on_target_created(ev.clone()),
424 CdpEvent::TargetAttachedToTarget(ref ev) => self.on_attached_to_target(ev.clone()),
425 CdpEvent::TargetTargetDestroyed(ref ev) => self.on_target_destroyed(ev.clone()),
426 CdpEvent::TargetDetachedFromTarget(ref ev) => self.on_detached_from_target(ev.clone()),
427 _ => {}
428 }
429
430 chromiumoxide_cdp::consume_event!(match params {
431 |ev| self.event_listeners.start_send(ev),
432 |json| { let _ = self.event_listeners.try_send_custom(&method, json);}
433 });
434 }
435
436 fn on_target_created(&mut self, event: EventTargetCreated) {
440 if !self.browser_contexts.is_empty() {
441 if let Some(ref context_id) = event.target_info.browser_context_id {
442 let bc = BrowserContext {
443 id: Some(context_id.clone()),
444 };
445 if !self.browser_contexts.contains(&bc) {
446 return;
447 }
448 }
449 }
450 let browser_ctx = event
451 .target_info
452 .browser_context_id
453 .clone()
454 .map(BrowserContext::from)
455 .unwrap_or_else(|| self.default_browser_context.clone());
456 let target = Target::new(
457 event.target_info,
458 TargetConfig {
459 ignore_https_errors: self.config.ignore_https_errors,
460 request_timeout: self.config.request_timeout,
461 viewport: self.config.viewport.clone(),
462 request_intercept: self.config.request_intercept,
463 cache_enabled: self.config.cache_enabled,
464 service_worker_enabled: self.config.service_worker_enabled,
465 ignore_visuals: self.config.ignore_visuals,
466 ignore_stylesheets: self.config.ignore_stylesheets,
467 ignore_javascript: self.config.ignore_javascript,
468 ignore_analytics: self.config.ignore_analytics,
469 extra_headers: self.config.extra_headers.clone(),
470 only_html: self.config.only_html && self.config.created_first_target,
471 intercept_manager: self.config.intercept_manager,
472 max_bytes_allowed: self.config.max_bytes_allowed,
473 },
474 browser_ctx,
475 );
476
477 self.target_ids.push(target.target_id().clone());
478 self.targets.insert(target.target_id().clone(), target);
479 }
480
481 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 self.attached_targets.remove(&event.target_id);
505
506 if let Some(target) = self.targets.remove(&event.target_id) {
507 if let Some(session) = target.session_id() {
509 self.sessions.remove(session);
510 }
511 }
512 }
513
514 fn evict_timed_out_commands(&mut self, now: Instant) {
519 let timed_out = self
520 .pending_commands
521 .iter()
522 .filter(|(_, (_, _, timestamp))| now > (*timestamp + self.config.request_timeout))
523 .map(|(k, _)| *k)
524 .collect::<Vec<_>>();
525
526 for call in timed_out {
527 if let Some((req, _, _)) = self.pending_commands.remove(&call) {
528 match req {
529 PendingRequest::CreateTarget(tx) => {
530 let _ = tx.send(Err(CdpError::Timeout));
531 }
532 PendingRequest::GetTargets(tx) => {
533 let _ = tx.send(Err(CdpError::Timeout));
534 }
535 PendingRequest::Navigate(nav) => {
536 if let Some(nav) = self.navigations.remove(&nav) {
537 match nav {
538 NavigationRequest::Navigate(nav) => {
539 let _ = nav.tx.send(Err(CdpError::Timeout));
540 }
541 }
542 }
543 }
544 PendingRequest::ExternalCommand(tx) => {
545 let _ = tx.send(Err(CdpError::Timeout));
546 }
547 PendingRequest::InternalCommand(_) => {}
548 PendingRequest::CloseBrowser(tx) => {
549 let _ = tx.send(Err(CdpError::Timeout));
550 }
551 }
552 }
553 }
554 }
555
556 pub fn event_listeners_mut(&mut self) -> &mut EventListeners {
557 &mut self.event_listeners
558 }
559}
560
561impl Stream for Handler {
562 type Item = Result<()>;
563
564 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
565 let pin = self.get_mut();
566
567 let mut dispose = false;
568
569 loop {
570 let now = Instant::now();
571 while let Poll::Ready(Some(msg)) = Pin::new(&mut pin.from_browser).poll_next(cx) {
575 match msg {
576 HandlerMessage::Command(cmd) => {
577 pin.submit_external_command(cmd, now)?;
578 }
579 HandlerMessage::FetchTargets(tx) => {
580 pin.submit_fetch_targets(tx, now);
581 }
582 HandlerMessage::CloseBrowser(tx) => {
583 pin.submit_close(tx, now);
584 }
585 HandlerMessage::CreatePage(params, tx) => {
586 if let Some(ref id) = params.browser_context_id {
587 pin.browser_contexts
588 .insert(BrowserContext::from(id.clone()));
589 }
590 pin.create_page(params, tx);
591 }
592 HandlerMessage::GetPages(tx) => {
593 let pages: Vec<_> = pin
594 .targets
595 .values_mut()
596 .filter(|p: &&mut Target| p.is_page())
597 .filter_map(|target| target.get_or_create_page())
598 .map(|page| Page::from(page.clone()))
599 .collect();
600 let _ = tx.send(pages);
601 }
602 HandlerMessage::InsertContext(ctx) => {
603 if pin.default_browser_context.id().is_none() {
604 pin.default_browser_context = ctx.clone();
605 }
606 pin.browser_contexts.insert(ctx);
607 }
608 HandlerMessage::DisposeContext(ctx) => {
609 pin.browser_contexts.remove(&ctx);
610 pin.attached_targets.retain(|tid| {
611 pin.targets
612 .get(tid)
613 .and_then(|t| t.browser_context_id()) .map(|id| Some(id) != ctx.id())
615 .unwrap_or(true)
616 });
617 pin.closing = true;
618 dispose = true;
619 }
620 HandlerMessage::GetPage(target_id, tx) => {
621 let page = pin
622 .targets
623 .get_mut(&target_id)
624 .and_then(|target| target.get_or_create_page())
625 .map(|page| Page::from(page.clone()));
626 let _ = tx.send(page);
627 }
628 HandlerMessage::AddEventListener(req) => {
629 pin.event_listeners.add_listener(req);
630 }
631 }
632 }
633
634 for n in (0..pin.target_ids.len()).rev() {
635 let target_id = pin.target_ids.swap_remove(n);
636
637 if let Some((id, mut target)) = pin.targets.remove_entry(&target_id) {
638 while let Some(event) = target.poll(cx, now) {
639 match event {
640 TargetEvent::Request(req) => {
641 let _ = pin.submit_internal_command(
642 target.target_id().clone(),
643 req,
644 now,
645 );
646 }
647 TargetEvent::Command(msg) => {
648 pin.on_target_message(&mut target, msg, now);
649 }
650 TargetEvent::NavigationRequest(id, req) => {
651 pin.submit_navigation(id, req, now);
652 }
653 TargetEvent::NavigationResult(res) => {
654 pin.on_navigation_lifecycle_completed(res)
655 }
656 TargetEvent::BytesConsumed(n) => {
657 if let Some(rem) = pin.remaining_bytes.as_mut() {
658 *rem = rem.saturating_sub(n);
659 if *rem == 0 {
660 pin.budget_exhausted = true;
661 }
662 }
663 }
664 }
665 }
666
667 target.event_listeners_mut().poll(cx);
669 pin.event_listeners_mut().poll(cx);
671
672 pin.targets.insert(id, target);
673 pin.target_ids.push(target_id);
674 }
675 }
676
677 let mut done = true;
678
679 while let Poll::Ready(Some(ev)) = Pin::new(&mut pin.conn).poll_next(cx) {
680 match ev {
681 Ok(boxed_msg) => match *boxed_msg {
682 Message::Response(resp) => {
683 pin.on_response(resp);
684 if pin.closing {
685 return Poll::Ready(None);
686 }
687 }
688 Message::Event(ev) => {
689 pin.on_event(ev);
690 }
691 },
692 Err(err) => {
693 tracing::error!("WS Connection error: {:?}", err);
694 match err {
695 CdpError::Ws(ref ws_error) => match ws_error {
696 Error::AlreadyClosed => {
697 pin.closing = true;
698 dispose = true;
699 break;
700 }
701 Error::Protocol(detail)
702 if detail == &ProtocolError::ResetWithoutClosingHandshake =>
703 {
704 pin.closing = true;
705 dispose = true;
706 break;
707 }
708 _ => {}
709 },
710 _ => {}
711 };
712 return Poll::Ready(Some(Err(err)));
713 }
714 }
715 done = false;
716 }
717
718 if pin.evict_command_timeout.poll_ready(cx) {
719 pin.evict_timed_out_commands(now);
721 }
722
723 if pin.budget_exhausted {
724 for t in pin.targets.values_mut() {
725 t.network_manager.set_block_all(true);
726 }
727 }
728
729 if dispose {
730 return Poll::Ready(None);
731 }
732
733 if done {
734 return Poll::Pending;
736 }
737 }
738 }
739}
740
741#[derive(Debug, Clone)]
743pub struct HandlerConfig {
744 pub ignore_https_errors: bool,
746 pub viewport: Option<Viewport>,
748 pub context_ids: Vec<BrowserContextId>,
750 pub request_timeout: Duration,
752 pub request_intercept: bool,
754 pub cache_enabled: bool,
756 pub service_worker_enabled: bool,
758 pub ignore_visuals: bool,
760 pub ignore_stylesheets: bool,
762 pub ignore_javascript: bool,
764 pub ignore_analytics: bool,
766 pub ignore_ads: bool,
768 pub extra_headers: Option<std::collections::HashMap<String, String>>,
770 pub only_html: bool,
772 pub created_first_target: bool,
774 pub intercept_manager: NetworkInterceptManager,
776 pub max_bytes_allowed: Option<u64>,
778}
779
780impl Default for HandlerConfig {
781 fn default() -> Self {
782 Self {
783 ignore_https_errors: true,
784 viewport: Default::default(),
785 context_ids: Vec::new(),
786 request_timeout: Duration::from_millis(REQUEST_TIMEOUT),
787 request_intercept: false,
788 cache_enabled: true,
789 service_worker_enabled: true,
790 ignore_visuals: false,
791 ignore_stylesheets: false,
792 ignore_ads: false,
793 ignore_javascript: false,
794 ignore_analytics: true,
795 only_html: false,
796 extra_headers: Default::default(),
797 created_first_target: false,
798 intercept_manager: NetworkInterceptManager::Unknown,
799 max_bytes_allowed: None,
800 }
801 }
802}
803
804#[derive(Debug)]
806pub struct NavigationInProgress<T> {
807 navigated: bool,
809 response: Option<Response>,
811 tx: OneshotSender<T>,
813}
814
815impl<T> NavigationInProgress<T> {
816 fn new(tx: OneshotSender<T>) -> Self {
817 Self {
818 navigated: false,
819 response: None,
820 tx,
821 }
822 }
823
824 fn set_response(&mut self, resp: Response) {
826 self.response = Some(resp);
827 }
828
829 fn set_navigated(&mut self) {
831 self.navigated = true;
832 }
833}
834
835#[derive(Debug)]
837enum NavigationRequest {
838 Navigate(NavigationInProgress<Result<Response>>),
840 }
842
843#[derive(Debug)]
846enum PendingRequest {
847 CreateTarget(OneshotSender<Result<Page>>),
850 GetTargets(OneshotSender<Result<Vec<TargetInfo>>>),
852 Navigate(NavigationId),
859 ExternalCommand(OneshotSender<Result<Response>>),
861 InternalCommand(TargetId),
864 CloseBrowser(OneshotSender<Result<CloseReturns>>),
866}
867
868#[derive(Debug)]
872pub(crate) enum HandlerMessage {
873 CreatePage(CreateTargetParams, OneshotSender<Result<Page>>),
874 FetchTargets(OneshotSender<Result<Vec<TargetInfo>>>),
875 InsertContext(BrowserContext),
876 DisposeContext(BrowserContext),
877 GetPages(OneshotSender<Vec<Page>>),
878 Command(CommandMessage),
879 GetPage(TargetId, OneshotSender<Option<Page>>),
880 AddEventListener(EventListenerRequest),
881 CloseBrowser(OneshotSender<Result<CloseReturns>>),
882}