viewpoint_core/context/
mod.rs1use std::sync::Arc;
4
5use viewpoint_cdp::protocol::target::{
6 AttachToTargetParams, AttachToTargetResult, CreateTargetParams, CreateTargetResult,
7 DisposeBrowserContextParams,
8};
9use viewpoint_cdp::CdpConnection;
10use tracing::{debug, info, instrument, trace};
11
12use crate::error::ContextError;
13use crate::page::Page;
14
15#[derive(Debug)]
20pub struct BrowserContext {
21 connection: Arc<CdpConnection>,
23 context_id: String,
25 closed: bool,
27}
28
29impl BrowserContext {
30 pub(crate) fn new(connection: Arc<CdpConnection>, context_id: String) -> Self {
32 debug!(context_id = %context_id, "Created BrowserContext");
33 Self {
34 connection,
35 context_id,
36 closed: false,
37 }
38 }
39
40 #[instrument(level = "info", skip(self), fields(context_id = %self.context_id))]
46 pub async fn new_page(&self) -> Result<Page, ContextError> {
47 if self.closed {
48 return Err(ContextError::Closed);
49 }
50
51 info!("Creating new page");
52
53 debug!("Creating target via Target.createTarget");
55 let create_result: CreateTargetResult = self
56 .connection
57 .send_command(
58 "Target.createTarget",
59 Some(CreateTargetParams {
60 url: "about:blank".to_string(),
61 width: None,
62 height: None,
63 browser_context_id: Some(self.context_id.clone()),
64 background: None,
65 new_window: None,
66 }),
67 None,
68 )
69 .await?;
70
71 let target_id = &create_result.target_id;
72 debug!(target_id = %target_id, "Target created");
73
74 debug!(target_id = %target_id, "Attaching to target");
76 let attach_result: AttachToTargetResult = self
77 .connection
78 .send_command(
79 "Target.attachToTarget",
80 Some(AttachToTargetParams {
81 target_id: target_id.clone(),
82 flatten: Some(true),
83 }),
84 None,
85 )
86 .await?;
87
88 let session_id = &attach_result.session_id;
89 debug!(session_id = %session_id, "Attached to target");
90
91 trace!("Enabling Page domain");
93 self.connection
94 .send_command::<(), serde_json::Value>("Page.enable", None, Some(session_id))
95 .await?;
96
97 trace!("Enabling Network domain");
98 self.connection
99 .send_command::<(), serde_json::Value>("Network.enable", None, Some(session_id))
100 .await?;
101
102 trace!("Enabling Runtime domain");
103 self.connection
104 .send_command::<(), serde_json::Value>("Runtime.enable", None, Some(session_id))
105 .await?;
106
107 trace!("Enabling lifecycle events");
108 self.connection
109 .send_command::<_, serde_json::Value>(
110 "Page.setLifecycleEventsEnabled",
111 Some(viewpoint_cdp::protocol::page::SetLifecycleEventsEnabledParams {
112 enabled: true,
113 }),
114 Some(session_id),
115 )
116 .await?;
117
118 trace!("Getting frame tree");
120 let frame_tree: viewpoint_cdp::protocol::page::GetFrameTreeResult = self
121 .connection
122 .send_command("Page.getFrameTree", None::<()>, Some(session_id))
123 .await?;
124
125 let frame_id = frame_tree.frame_tree.frame.id.clone();
126 debug!(frame_id = %frame_id, "Got main frame ID");
127
128 info!(target_id = %target_id, session_id = %session_id, frame_id = %frame_id, "Page created successfully");
129
130 Ok(Page::new(
131 self.connection.clone(),
132 create_result.target_id,
133 attach_result.session_id,
134 frame_id,
135 ))
136 }
137
138 #[instrument(level = "info", skip(self), fields(context_id = %self.context_id))]
144 pub async fn close(&mut self) -> Result<(), ContextError> {
145 if self.closed {
146 debug!("Context already closed");
147 return Ok(());
148 }
149
150 info!("Closing browser context");
151
152 self.connection
153 .send_command::<_, serde_json::Value>(
154 "Target.disposeBrowserContext",
155 Some(DisposeBrowserContextParams {
156 browser_context_id: self.context_id.clone(),
157 }),
158 None,
159 )
160 .await?;
161
162 self.closed = true;
163 info!("Browser context closed");
164 Ok(())
165 }
166
167 pub fn id(&self) -> &str {
169 &self.context_id
170 }
171
172 pub fn is_closed(&self) -> bool {
174 self.closed
175 }
176
177 pub fn connection(&self) -> &Arc<CdpConnection> {
179 &self.connection
180 }
181}