1use std::collections::VecDeque;
2use std::pin::Pin;
3use std::sync::Arc;
4use std::time::Instant;
5
6use chromiumoxide_cdp::cdp::browser_protocol::target::DetachFromTargetParams;
7use futures::channel::oneshot::Sender;
8use futures::stream::Stream;
9use futures::task::{Context, Poll};
10
11use chromiumoxide_cdp::cdp::browser_protocol::page::{FrameId, GetFrameTreeParams};
12use chromiumoxide_cdp::cdp::browser_protocol::{
13 browser::BrowserContextId,
14 log as cdplog, performance,
15 target::{AttachToTargetParams, SessionId, SetAutoAttachParams, TargetId, TargetInfo},
16};
17use chromiumoxide_cdp::cdp::events::CdpEvent;
18use chromiumoxide_cdp::cdp::CdpEventMessage;
19use chromiumoxide_types::{Command, Method, Request, Response};
20
21use super::blockers::intercept_manager::NetworkInterceptManager;
22use crate::auth::Credentials;
23use crate::cdp::browser_protocol::target::CloseTargetParams;
24use crate::cmd::CommandChain;
25use crate::cmd::CommandMessage;
26use crate::error::{CdpError, Result};
27use crate::handler::browser::BrowserContext;
28use crate::handler::domworld::DOMWorldKind;
29use crate::handler::emulation::EmulationManager;
30use crate::handler::frame::{
31 FrameEvent, FrameManager, NavigationError, NavigationId, NavigationOk,
32};
33use crate::handler::frame::{FrameRequestedNavigation, UTILITY_WORLD_NAME};
34use crate::handler::network::{NetworkEvent, NetworkManager};
35use crate::handler::page::PageHandle;
36use crate::handler::viewport::Viewport;
37use crate::handler::{PageInner, REQUEST_TIMEOUT};
38use crate::listeners::{EventListenerRequest, EventListeners};
39use crate::{page::Page, ArcHttpRequest};
40use chromiumoxide_cdp::cdp::js_protocol::runtime::{
41 ExecutionContextId, RunIfWaitingForDebuggerParams,
42};
43use std::time::Duration;
44
45macro_rules! advance_state {
46 ($s:ident, $cx:ident, $now:ident, $cmds: ident, $next_state:expr ) => {{
47 if let Poll::Ready(poll) = $cmds.poll($now) {
48 return match poll {
49 None => {
50 $s.init_state = $next_state;
51 $s.poll($cx, $now)
52 }
53 Some(Ok((method, params))) => Some(TargetEvent::Request(Request {
54 method,
55 session_id: $s.session_id.clone().map(Into::into),
56 params,
57 })),
58 Some(Err(_)) => Some($s.on_initialization_failed()),
59 };
60 } else {
61 return None;
62 }
63 }};
64}
65
66lazy_static::lazy_static! {
67 static ref INIT_COMMANDS_PARAMS: Vec<(chromiumoxide_types::MethodId, serde_json::Value)> = {
69 let attach = SetAutoAttachParams::builder()
70 .flatten(true)
71 .auto_attach(true)
72 .wait_for_debugger_on_start(true)
73 .build()
74 .unwrap();
75 let enable_performance = performance::EnableParams::default();
76 let enable_log = cdplog::EnableParams::default();
77
78 vec![
79 (
80 attach.identifier(),
81 serde_json::to_value(attach).unwrap_or_default(),
82 ),
83 (
84 enable_performance.identifier(),
85 serde_json::to_value(enable_performance).unwrap_or_default(),
86 ),
87 (
88 enable_log.identifier(),
89 serde_json::to_value(enable_log).unwrap_or_default(),
90 )
91 ]
92 };
93}
94
95#[derive(Debug)]
96pub struct Target {
97 info: TargetInfo,
99 r#type: TargetType,
101 config: TargetConfig,
103 browser_context: BrowserContext,
105 frame_manager: FrameManager,
108 network_manager: NetworkManager,
110 emulation_manager: EmulationManager,
111 session_id: Option<SessionId>,
113 page: Option<PageHandle>,
115 pub(crate) init_state: TargetInit,
117 queued_events: VecDeque<TargetEvent>,
119 event_listeners: EventListeners,
121 wait_for_frame_navigation: Vec<Sender<ArcHttpRequest>>,
123 initiator: Option<Sender<Result<Page>>>,
125}
126
127impl Target {
128 pub fn new(info: TargetInfo, config: TargetConfig, browser_context: BrowserContext) -> Self {
131 let ty = TargetType::new(&info.r#type);
132 let request_timeout = config.request_timeout;
133 let mut network_manager = NetworkManager::new(config.ignore_https_errors, request_timeout);
134
135 if !config.cache_enabled {
136 network_manager.set_cache_enabled(false);
137 }
138
139 if !config.service_worker_enabled {
140 network_manager.set_service_worker_enabled(true);
141 }
142
143 network_manager.set_request_interception(config.request_intercept);
144
145 if let Some(ref headers) = config.extra_headers {
146 network_manager.set_extra_headers(headers.clone());
147 }
148
149 network_manager.ignore_visuals = config.ignore_visuals;
150 network_manager.block_javascript = config.ignore_javascript;
151 network_manager.block_analytics = config.ignore_analytics;
152 network_manager.block_stylesheets = config.ignore_stylesheets;
153 network_manager.only_html = config.only_html;
154 network_manager.intercept_manager = config.intercept_manager;
155
156 Self {
157 info,
158 r#type: ty,
159 config,
160 frame_manager: FrameManager::new(request_timeout),
161 network_manager,
162 emulation_manager: EmulationManager::new(request_timeout),
163 session_id: None,
164 page: None,
165 init_state: TargetInit::AttachToTarget,
166 wait_for_frame_navigation: Default::default(),
167 queued_events: Default::default(),
168 event_listeners: Default::default(),
169 initiator: None,
170 browser_context,
171 }
172 }
173
174 pub fn set_session_id(&mut self, id: SessionId) {
175 self.session_id = Some(id)
176 }
177
178 pub fn session_id(&self) -> Option<&SessionId> {
179 self.session_id.as_ref()
180 }
181
182 pub fn browser_context(&self) -> &BrowserContext {
183 &self.browser_context
184 }
185
186 pub fn session_id_mut(&mut self) -> &mut Option<SessionId> {
187 &mut self.session_id
188 }
189
190 pub fn target_id(&self) -> &TargetId {
192 &self.info.target_id
193 }
194
195 pub fn r#type(&self) -> &TargetType {
197 &self.r#type
198 }
199
200 pub fn is_initialized(&self) -> bool {
202 matches!(self.init_state, TargetInit::Initialized)
203 }
204
205 pub fn goto(&mut self, req: FrameRequestedNavigation) {
207 self.frame_manager.goto(req)
208 }
209
210 fn create_page(&mut self) {
211 if self.page.is_none() {
212 if let Some(session) = self.session_id.clone() {
213 let handle =
214 PageHandle::new(self.target_id().clone(), session, self.opener_id().cloned());
215 self.page = Some(handle);
216 }
217 }
218 }
219
220 pub(crate) fn get_or_create_page(&mut self) -> Option<&Arc<PageInner>> {
222 self.create_page();
223 self.page.as_ref().map(|p| p.inner())
224 }
225
226 pub fn is_page(&self) -> bool {
227 self.r#type().is_page()
228 }
229
230 pub fn browser_context_id(&self) -> Option<&BrowserContextId> {
231 self.info.browser_context_id.as_ref()
232 }
233
234 pub fn info(&self) -> &TargetInfo {
235 &self.info
236 }
237
238 pub fn opener_id(&self) -> Option<&TargetId> {
240 self.info.opener_id.as_ref()
241 }
242
243 pub fn frame_manager(&self) -> &FrameManager {
244 &self.frame_manager
245 }
246
247 pub fn frame_manager_mut(&mut self) -> &mut FrameManager {
248 &mut self.frame_manager
249 }
250
251 pub fn event_listeners_mut(&mut self) -> &mut EventListeners {
252 &mut self.event_listeners
253 }
254
255 pub fn on_response(&mut self, resp: Response, method: &str) {
257 if let Some(cmds) = self.init_state.commands_mut() {
258 cmds.received_response(method);
259 }
260 #[allow(clippy::single_match)] match method {
262 GetFrameTreeParams::IDENTIFIER => {
263 if let Some(resp) = resp
264 .result
265 .and_then(|val| GetFrameTreeParams::response_from_value(val).ok())
266 {
267 self.frame_manager.on_frame_tree(resp.frame_tree);
268 }
269 }
270 _ => {}
273 }
274 }
275
276 pub fn on_event(&mut self, event: CdpEventMessage) {
277 let CdpEventMessage { params, method, .. } = event;
278
279 match ¶ms {
280 CdpEvent::PageFrameAttached(ev) => self
282 .frame_manager
283 .on_frame_attached(ev.frame_id.clone(), Some(ev.parent_frame_id.clone())),
284 CdpEvent::PageFrameDetached(ev) => self.frame_manager.on_frame_detached(ev),
285 CdpEvent::PageFrameNavigated(ev) => self.frame_manager.on_frame_navigated(&ev.frame),
286 CdpEvent::PageNavigatedWithinDocument(ev) => {
287 self.frame_manager.on_frame_navigated_within_document(ev)
288 }
289 CdpEvent::RuntimeExecutionContextCreated(ev) => {
290 self.frame_manager.on_frame_execution_context_created(ev)
291 }
292 CdpEvent::RuntimeExecutionContextDestroyed(ev) => {
293 self.frame_manager.on_frame_execution_context_destroyed(ev)
294 }
295 CdpEvent::RuntimeExecutionContextsCleared(_) => {
296 self.frame_manager.on_execution_contexts_cleared()
297 }
298 CdpEvent::RuntimeBindingCalled(ev) => {
299 self.frame_manager.on_runtime_binding_called(ev)
301 }
302 CdpEvent::PageLifecycleEvent(ev) => self.frame_manager.on_page_lifecycle_event(ev),
303 CdpEvent::PageFrameStartedLoading(ev) => {
304 self.frame_manager.on_frame_started_loading(ev);
305 }
306
307 CdpEvent::TargetAttachedToTarget(ev) => {
309 if ev.waiting_for_debugger {
310 let runtime_cmd = RunIfWaitingForDebuggerParams::default();
311
312 self.queued_events.push_back(TargetEvent::Request(Request {
313 method: runtime_cmd.identifier(),
314 session_id: Some(ev.session_id.clone().into()),
315 params: serde_json::to_value(runtime_cmd).unwrap_or_default(),
316 }));
317 }
318
319 if "service_worker" == &ev.target_info.r#type {
320 let detach_command = DetachFromTargetParams::builder()
321 .session_id(ev.session_id.clone())
322 .build();
323
324 self.queued_events.push_back(TargetEvent::Request(Request {
325 method: detach_command.identifier(),
326 session_id: self.session_id.clone().map(Into::into),
327 params: serde_json::to_value(detach_command).unwrap_or_default(),
328 }));
329 }
330 }
331
332 CdpEvent::FetchRequestPaused(ev) => self.network_manager.on_fetch_request_paused(ev),
334 CdpEvent::FetchAuthRequired(ev) => self.network_manager.on_fetch_auth_required(ev),
335 CdpEvent::NetworkRequestWillBeSent(ev) => {
336 self.network_manager.on_request_will_be_sent(ev)
337 }
338 CdpEvent::NetworkRequestServedFromCache(ev) => {
339 self.network_manager.on_request_served_from_cache(ev)
340 }
341 CdpEvent::NetworkResponseReceived(ev) => self.network_manager.on_response_received(ev),
342 CdpEvent::NetworkLoadingFinished(ev) => {
343 self.network_manager.on_network_loading_finished(ev)
344 }
345 CdpEvent::NetworkLoadingFailed(ev) => {
346 self.network_manager.on_network_loading_failed(ev)
347 }
348 _ => (),
349 }
350 chromiumoxide_cdp::consume_event!(match params {
351 |ev| self.event_listeners.start_send(ev),
352 |json| { let _ = self.event_listeners.try_send_custom(&method, json);}
353 });
354 }
355
356 fn on_initialization_failed(&mut self) -> TargetEvent {
358 if let Some(initiator) = self.initiator.take() {
359 let _ = initiator.send(Err(CdpError::Timeout));
360 }
361 self.init_state = TargetInit::Closing;
362 let close_target = CloseTargetParams::new(self.info.target_id.clone());
363 TargetEvent::Request(Request {
364 method: close_target.identifier(),
365 session_id: self.session_id.clone().map(Into::into),
366 params: serde_json::to_value(close_target).unwrap_or_default(),
367 })
368 }
369
370 pub(crate) fn poll(&mut self, cx: &mut Context<'_>, now: Instant) -> Option<TargetEvent> {
372 if !self.is_page() {
373 return None;
375 }
376
377 match &mut self.init_state {
378 TargetInit::AttachToTarget => {
379 self.init_state = TargetInit::InitializingFrame(FrameManager::init_commands(
380 self.config.request_timeout,
381 ));
382
383 if let Ok(params) = AttachToTargetParams::builder()
384 .target_id(self.target_id().clone())
385 .flatten(true)
386 .build()
387 {
388 return Some(TargetEvent::Request(Request::new(
389 params.identifier(),
390 serde_json::to_value(params).unwrap_or_default(),
391 )));
392 } else {
393 return None;
394 }
395 }
396 TargetInit::InitializingFrame(cmds) => {
397 self.session_id.as_ref()?;
398 if let Poll::Ready(poll) = cmds.poll(now) {
399 return match poll {
400 None => {
401 if let Some(isolated_world_cmds) =
402 self.frame_manager.ensure_isolated_world(UTILITY_WORLD_NAME)
403 {
404 *cmds = isolated_world_cmds;
405 } else {
406 self.init_state = TargetInit::InitializingNetwork(
407 self.network_manager.init_commands(),
408 );
409 }
410 self.poll(cx, now)
411 }
412 Some(Ok((method, params))) => Some(TargetEvent::Request(Request {
413 method,
414 session_id: self.session_id.clone().map(Into::into),
415 params,
416 })),
417 Some(Err(_)) => Some(self.on_initialization_failed()),
418 };
419 } else {
420 return None;
421 }
422 }
423 TargetInit::InitializingNetwork(cmds) => {
424 advance_state!(
425 self,
426 cx,
427 now,
428 cmds,
429 TargetInit::InitializingPage(Self::page_init_commands(
430 self.config.request_timeout
431 ))
432 );
433 }
434 TargetInit::InitializingPage(cmds) => {
435 advance_state!(
436 self,
437 cx,
438 now,
439 cmds,
440 match self.config.viewport.as_ref() {
441 Some(viewport) => TargetInit::InitializingEmulation(
442 self.emulation_manager.init_commands(viewport)
443 ),
444 None => TargetInit::Initialized,
445 }
446 );
447 }
448 TargetInit::InitializingEmulation(cmds) => {
449 advance_state!(self, cx, now, cmds, TargetInit::Initialized);
450 }
451 TargetInit::Initialized => {
452 if let Some(initiator) = self.initiator.take() {
453 if self
455 .frame_manager
456 .main_frame()
457 .map(|frame| frame.is_loaded())
458 .unwrap_or_default()
459 {
460 if let Some(page) = self.get_or_create_page() {
461 let _ = initiator.send(Ok(page.clone().into()));
462 } else {
463 self.initiator = Some(initiator);
464 }
465 } else {
466 self.initiator = Some(initiator);
467 }
468 }
469 }
470 TargetInit::Closing => return None,
471 };
472
473 loop {
474 if self.init_state == TargetInit::Closing {
475 break None;
476 }
477
478 if let Some(frame) = self.frame_manager.main_frame() {
479 if frame.is_loaded() {
480 while let Some(tx) = self.wait_for_frame_navigation.pop() {
481 let _ = tx.send(frame.http_request().cloned());
482 }
483 }
484 }
485
486 if let Some(ev) = self.queued_events.pop_front() {
488 return Some(ev);
489 }
490
491 if let Some(handle) = self.page.as_mut() {
492 while let Poll::Ready(Some(msg)) = Pin::new(&mut handle.rx).poll_next(cx) {
493 if self.init_state == TargetInit::Closing {
494 break;
495 }
496
497 match msg {
498 TargetMessage::Command(cmd) => {
499 self.queued_events.push_back(TargetEvent::Command(cmd));
500 }
501 TargetMessage::MainFrame(tx) => {
502 let _ =
503 tx.send(self.frame_manager.main_frame().map(|f| f.id().clone()));
504 }
505 TargetMessage::AllFrames(tx) => {
506 let _ = tx.send(
507 self.frame_manager
508 .frames()
509 .map(|f| f.id().clone())
510 .collect(),
511 );
512 }
513 TargetMessage::Url(req) => {
514 let GetUrl { frame_id, tx } = req;
515 let frame = if let Some(frame_id) = frame_id {
516 self.frame_manager.frame(&frame_id)
517 } else {
518 self.frame_manager.main_frame()
519 };
520 let _ = tx.send(frame.and_then(|f| f.url().map(str::to_string)));
521 }
522 TargetMessage::Name(req) => {
523 let GetName { frame_id, tx } = req;
524 let frame = if let Some(frame_id) = frame_id {
525 self.frame_manager.frame(&frame_id)
526 } else {
527 self.frame_manager.main_frame()
528 };
529 let _ = tx.send(frame.and_then(|f| f.name().map(str::to_string)));
530 }
531 TargetMessage::Parent(req) => {
532 let GetParent { frame_id, tx } = req;
533 let frame = self.frame_manager.frame(&frame_id);
534 let _ = tx.send(frame.and_then(|f| f.parent_id().cloned()));
535 }
536 TargetMessage::WaitForNavigation(tx) => {
537 if let Some(frame) = self.frame_manager.main_frame() {
538 if frame.is_loaded() {
542 let _ = tx.send(frame.http_request().cloned());
543 } else {
544 self.wait_for_frame_navigation.push(tx);
545 }
546 } else {
547 self.wait_for_frame_navigation.push(tx);
548 }
549 }
550 TargetMessage::AddEventListener(req) => {
551 self.event_listeners.add_listener(req);
553 }
554 TargetMessage::GetExecutionContext(ctx) => {
555 let GetExecutionContext {
556 dom_world,
557 frame_id,
558 tx,
559 } = ctx;
560 let frame = if let Some(frame_id) = frame_id {
561 self.frame_manager.frame(&frame_id)
562 } else {
563 self.frame_manager.main_frame()
564 };
565
566 if let Some(frame) = frame {
567 match dom_world {
568 DOMWorldKind::Main => {
569 let _ = tx.send(frame.main_world().execution_context());
570 }
571 DOMWorldKind::Secondary => {
572 let _ =
573 tx.send(frame.secondary_world().execution_context());
574 }
575 }
576 } else {
577 let _ = tx.send(None);
578 }
579 }
580 TargetMessage::Authenticate(credentials) => {
581 self.network_manager.authenticate(credentials);
582 }
583 }
584 }
585 }
586
587 while let Some(event) = self.network_manager.poll() {
588 if self.init_state == TargetInit::Closing {
589 break;
590 }
591 match event {
592 NetworkEvent::SendCdpRequest((method, params)) => {
593 self.queued_events.push_back(TargetEvent::Request(Request {
595 method,
596 session_id: self.session_id.clone().map(Into::into),
597 params,
598 }))
599 }
600 NetworkEvent::Request(_) => {}
601 NetworkEvent::Response(_) => {}
602 NetworkEvent::RequestFailed(request) => {
603 self.frame_manager.on_http_request_finished(request);
604 }
605 NetworkEvent::RequestFinished(request) => {
606 self.frame_manager.on_http_request_finished(request);
607 }
608 }
609 }
610
611 while let Some(event) = self.frame_manager.poll(now) {
612 if self.init_state == TargetInit::Closing {
613 break;
614 }
615 match event {
616 FrameEvent::NavigationResult(res) => {
617 self.queued_events
618 .push_back(TargetEvent::NavigationResult(res));
619 }
620 FrameEvent::NavigationRequest(id, req) => {
621 self.queued_events
622 .push_back(TargetEvent::NavigationRequest(id, req));
623 }
624 }
625 }
626
627 if self.queued_events.is_empty() {
628 return None;
629 }
630 }
631 }
632
633 pub fn set_initiator(&mut self, tx: Sender<Result<Page>>) {
636 self.initiator = Some(tx);
637 }
638
639 pub(crate) fn page_init_commands(timeout: Duration) -> CommandChain {
640 CommandChain::new(INIT_COMMANDS_PARAMS.clone(), timeout)
641 }
642}
643
644#[derive(Debug, Clone)]
645pub struct TargetConfig {
646 pub ignore_https_errors: bool,
647 pub request_timeout: Duration,
649 pub viewport: Option<Viewport>,
650 pub request_intercept: bool,
651 pub cache_enabled: bool,
652 pub ignore_visuals: bool,
653 pub ignore_javascript: bool,
654 pub ignore_analytics: bool,
655 pub ignore_stylesheets: bool,
656 pub only_html: bool,
657 pub service_worker_enabled: bool,
658 pub extra_headers: Option<std::collections::HashMap<String, String>>,
659 pub intercept_manager: NetworkInterceptManager,
660}
661
662impl Default for TargetConfig {
663 fn default() -> Self {
664 Self {
665 ignore_https_errors: true,
666 request_timeout: Duration::from_secs(REQUEST_TIMEOUT),
667 viewport: Default::default(),
668 request_intercept: false,
669 cache_enabled: true,
670 service_worker_enabled: true,
671 ignore_javascript: false,
672 ignore_visuals: false,
673 ignore_stylesheets: false,
674 ignore_analytics: true,
675 only_html: false,
676 extra_headers: Default::default(),
677 intercept_manager: NetworkInterceptManager::UNKNOWN,
678 }
679 }
680}
681
682#[derive(Debug, Clone, Eq, PartialEq)]
683pub enum TargetType {
684 Page,
685 BackgroundPage,
686 ServiceWorker,
687 SharedWorker,
688 Other,
689 Browser,
690 Webview,
691 Unknown(String),
692}
693
694impl TargetType {
695 pub fn new(ty: &str) -> Self {
696 match ty {
697 "page" => TargetType::Page,
698 "background_page" => TargetType::BackgroundPage,
699 "service_worker" => TargetType::ServiceWorker,
700 "shared_worker" => TargetType::SharedWorker,
701 "other" => TargetType::Other,
702 "browser" => TargetType::Browser,
703 "webview" => TargetType::Webview,
704 s => TargetType::Unknown(s.to_string()),
705 }
706 }
707
708 pub fn is_page(&self) -> bool {
709 matches!(self, TargetType::Page)
710 }
711
712 pub fn is_background_page(&self) -> bool {
713 matches!(self, TargetType::BackgroundPage)
714 }
715
716 pub fn is_service_worker(&self) -> bool {
717 matches!(self, TargetType::ServiceWorker)
718 }
719
720 pub fn is_shared_worker(&self) -> bool {
721 matches!(self, TargetType::SharedWorker)
722 }
723
724 pub fn is_other(&self) -> bool {
725 matches!(self, TargetType::Other)
726 }
727
728 pub fn is_browser(&self) -> bool {
729 matches!(self, TargetType::Browser)
730 }
731
732 pub fn is_webview(&self) -> bool {
733 matches!(self, TargetType::Webview)
734 }
735}
736
737#[derive(Debug)]
738pub(crate) enum TargetEvent {
739 Request(Request),
741 NavigationRequest(NavigationId, Request),
743 NavigationResult(Result<NavigationOk, NavigationError>),
745 Command(CommandMessage),
747}
748
749#[derive(Debug, PartialEq)]
751pub enum TargetInit {
752 InitializingFrame(CommandChain),
753 InitializingNetwork(CommandChain),
754 InitializingPage(CommandChain),
755 InitializingEmulation(CommandChain),
756 AttachToTarget,
757 Initialized,
758 Closing,
759}
760
761impl TargetInit {
762 fn commands_mut(&mut self) -> Option<&mut CommandChain> {
763 match self {
764 TargetInit::InitializingFrame(cmd) => Some(cmd),
765 TargetInit::InitializingNetwork(cmd) => Some(cmd),
766 TargetInit::InitializingPage(cmd) => Some(cmd),
767 TargetInit::InitializingEmulation(cmd) => Some(cmd),
768 TargetInit::AttachToTarget => None,
769 TargetInit::Initialized => None,
770 TargetInit::Closing => None,
771 }
772 }
773}
774
775#[derive(Debug)]
776pub struct GetExecutionContext {
777 pub dom_world: DOMWorldKind,
779 pub frame_id: Option<FrameId>,
781 pub tx: Sender<Option<ExecutionContextId>>,
783}
784
785impl GetExecutionContext {
786 pub fn new(tx: Sender<Option<ExecutionContextId>>) -> Self {
787 Self {
788 dom_world: DOMWorldKind::Main,
789 frame_id: None,
790 tx,
791 }
792 }
793}
794
795#[derive(Debug)]
796pub struct GetUrl {
797 pub frame_id: Option<FrameId>,
799 pub tx: Sender<Option<String>>,
801}
802
803impl GetUrl {
804 pub fn new(tx: Sender<Option<String>>) -> Self {
805 Self { frame_id: None, tx }
806 }
807}
808
809#[derive(Debug)]
810pub struct GetName {
811 pub frame_id: Option<FrameId>,
813 pub tx: Sender<Option<String>>,
815}
816
817#[derive(Debug)]
818pub struct GetParent {
819 pub frame_id: FrameId,
821 pub tx: Sender<Option<FrameId>>,
823}
824
825#[derive(Debug)]
826pub enum TargetMessage {
827 Command(CommandMessage),
829 MainFrame(Sender<Option<FrameId>>),
831 AllFrames(Sender<Vec<FrameId>>),
833 Url(GetUrl),
835 Name(GetName),
837 Parent(GetParent),
839 WaitForNavigation(Sender<ArcHttpRequest>),
841 AddEventListener(EventListenerRequest),
844 GetExecutionContext(GetExecutionContext),
846 Authenticate(Credentials),
847}