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
36pub 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#[must_use = "streams do nothing unless polled"]
58#[derive(Debug)]
59pub struct Handler {
60 pending_commands: FnvHashMap<CallId, (PendingRequest, MethodId, Instant)>,
64 from_browser: Fuse<Receiver<HandlerMessage>>,
66 pub default_browser_context: BrowserContext,
67 pub browser_contexts: HashSet<BrowserContext>,
68 target_ids: Vec<TargetId>,
70 targets: HashMap<TargetId, Target>,
72 navigations: FnvHashMap<NavigationId, NavigationRequest>,
74 sessions: HashMap<SessionId, Session>,
78 conn: Connection<CdpEventMessage>,
80 evict_command_timeout: PeriodicJob,
82 next_navigation_id: usize,
84 config: HandlerConfig,
86 event_listeners: EventListeners,
88 closing: bool,
90}
91
92lazy_static::lazy_static! {
93 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 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 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 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 pub fn get_target(&self, target_id: &TargetId) -> Option<&Target> {
147 self.targets.get(target_id)
148 }
149
150 pub fn targets(&self) -> impl Iterator<Item = &Target> + '_ {
152 self.targets.values()
153 }
154
155 pub fn default_browser_context(&self) -> &BrowserContext {
157 &self.default_browser_context
158 }
159
160 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 pub fn browser_contexts(&self) -> impl Iterator<Item = &BrowserContext> + '_ {
172 self.browser_contexts.iter()
173 }
174
175 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 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 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 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 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 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 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 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 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 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 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 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 fn on_detached_from_target(&mut self, event: EventDetachedFromTarget) {
508 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 fn on_target_destroyed(&mut self, event: EventTargetDestroyed) {
518 if let Some(target) = self.targets.remove(&event.target_id) {
519 if let Some(session) = target.session_id() {
521 self.sessions.remove(session);
522 }
523 }
524 }
525
526 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 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 target.event_listeners_mut().poll(cx);
662 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 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 pin.evict_timed_out_commands(now);
713 }
714
715 if dispose {
716 return Poll::Ready(None);
717 }
718
719 if done {
720 return Poll::Pending;
722 }
723 }
724 }
725}
726
727#[derive(Debug, Clone)]
729pub struct HandlerConfig {
730 pub ignore_https_errors: bool,
732 pub viewport: Option<Viewport>,
734 pub context_ids: Vec<BrowserContextId>,
736 pub request_timeout: Duration,
738 pub request_intercept: bool,
740 pub cache_enabled: bool,
742 pub service_worker_enabled: bool,
744 pub ignore_visuals: bool,
746 pub ignore_stylesheets: bool,
748 pub ignore_javascript: bool,
750 pub ignore_analytics: bool,
752 pub ignore_ads: bool,
754 pub extra_headers: Option<std::collections::HashMap<String, String>>,
756 pub only_html: bool,
758 pub created_first_target: bool,
760 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#[derive(Debug)]
789pub struct NavigationInProgress<T> {
790 navigated: bool,
792 response: Option<Response>,
794 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 fn set_response(&mut self, resp: Response) {
809 self.response = Some(resp);
810 }
811
812 fn set_navigated(&mut self) {
814 self.navigated = true;
815 }
816}
817
818#[derive(Debug)]
820enum NavigationRequest {
821 Navigate(NavigationInProgress<Result<Response>>),
823 }
825
826#[derive(Debug)]
829enum PendingRequest {
830 CreateTarget(OneshotSender<Result<Page>>),
833 GetTargets(OneshotSender<Result<Vec<TargetInfo>>>),
835 Navigate(NavigationId),
842 ExternalCommand(OneshotSender<Result<Response>>),
844 InternalCommand(TargetId),
847 CloseBrowser(OneshotSender<Result<CloseReturns>>),
849}
850
851#[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}