viewpoint_core/page/evaluate/
mod.rs1use std::time::Duration;
6
7use serde::{de::DeserializeOwned, Serialize};
8use tracing::{debug, instrument, trace};
9use viewpoint_cdp::protocol::runtime::{
10 CallFunctionOnParams, EvaluateParams, EvaluateResult, ReleaseObjectParams,
11};
12
13use crate::error::PageError;
14
15use super::Page;
16
17mod wait;
18
19pub use wait::{Polling, WaitForFunctionBuilder};
20
21pub(super) const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
23
24#[derive(Debug)]
29pub struct JsHandle {
30 object_id: String,
32 page_session_id: String,
34 connection: std::sync::Arc<viewpoint_cdp::CdpConnection>,
36}
37
38impl JsHandle {
39 pub(crate) fn new(
41 object_id: String,
42 page_session_id: String,
43 connection: std::sync::Arc<viewpoint_cdp::CdpConnection>,
44 ) -> Self {
45 Self {
46 object_id,
47 page_session_id,
48 connection,
49 }
50 }
51
52 pub fn object_id(&self) -> &str {
54 &self.object_id
55 }
56
57 pub async fn json_value<T: DeserializeOwned>(&self) -> Result<T, PageError> {
63 let params = CallFunctionOnParams {
64 function_declaration: "function() { return this; }".to_string(),
65 object_id: Some(self.object_id.clone()),
66 arguments: None,
67 silent: Some(false),
68 return_by_value: Some(true),
69 generate_preview: None,
70 user_gesture: None,
71 await_promise: Some(true),
72 execution_context_id: None,
73 object_group: None,
74 throw_on_side_effect: None,
75 unique_context_id: None,
76 serialization_options: None,
77 };
78
79 let result: viewpoint_cdp::protocol::runtime::CallFunctionOnResult = self
80 .connection
81 .send_command(
82 "Runtime.callFunctionOn",
83 Some(params),
84 Some(&self.page_session_id),
85 )
86 .await?;
87
88 if let Some(exception) = result.exception_details {
89 return Err(PageError::EvaluationFailed(exception.text));
90 }
91
92 let value = result.result.value.unwrap_or(serde_json::Value::Null);
94
95 serde_json::from_value(value)
96 .map_err(|e| PageError::EvaluationFailed(format!("Failed to deserialize: {e}")))
97 }
98
99 pub async fn dispose(self) -> Result<(), PageError> {
105 self.connection
106 .send_command::<_, serde_json::Value>(
107 "Runtime.releaseObject",
108 Some(ReleaseObjectParams {
109 object_id: self.object_id,
110 }),
111 Some(&self.page_session_id),
112 )
113 .await?;
114 Ok(())
115 }
116}
117
118impl Page {
119 pub(crate) async fn evaluate_js_raw(
134 &self,
135 expression: &str,
136 ) -> Result<serde_json::Value, PageError> {
137 if self.closed {
138 return Err(PageError::Closed);
139 }
140
141 let params = EvaluateParams {
142 expression: expression.to_string(),
143 object_group: None,
144 include_command_line_api: None,
145 silent: Some(true),
146 context_id: None,
147 return_by_value: Some(true),
148 await_promise: Some(false),
149 };
150
151 let result: EvaluateResult = self
152 .connection
153 .send_command("Runtime.evaluate", Some(params), Some(&self.session_id))
154 .await?;
155
156 if let Some(exception) = result.exception_details {
157 return Err(PageError::EvaluationFailed(exception.text));
158 }
159
160 result
161 .result
162 .value
163 .ok_or_else(|| PageError::EvaluationFailed("No result value".to_string()))
164 }
165
166 #[instrument(level = "debug", skip(self), fields(expression = %expression))]
205 pub async fn evaluate<T: DeserializeOwned>(&self, expression: &str) -> Result<T, PageError> {
206 self.evaluate_internal(expression, None, DEFAULT_TIMEOUT)
207 .await
208 }
209
210 #[instrument(level = "debug", skip(self, arg), fields(expression = %expression))]
228 pub async fn evaluate_with_arg<T: DeserializeOwned, A: Serialize>(
229 &self,
230 expression: &str,
231 arg: A,
232 ) -> Result<T, PageError> {
233 let arg_json = serde_json::to_value(arg).map_err(|e| {
234 PageError::EvaluationFailed(format!("Failed to serialize argument: {e}"))
235 })?;
236
237 self.evaluate_internal(expression, Some(arg_json), DEFAULT_TIMEOUT)
238 .await
239 }
240
241 #[instrument(level = "debug", skip(self), fields(expression = %expression))]
262 pub async fn evaluate_handle(&self, expression: &str) -> Result<JsHandle, PageError> {
263 if self.closed {
264 return Err(PageError::Closed);
265 }
266
267 debug!("Evaluating expression for handle");
268
269 let wrapped = wrap_expression(expression);
271
272 let params = EvaluateParams {
273 expression: wrapped,
274 object_group: Some("viewpoint".to_string()),
275 include_command_line_api: None,
276 silent: Some(false),
277 context_id: None,
278 return_by_value: Some(false), await_promise: Some(true),
280 };
281
282 let result: EvaluateResult = self
283 .connection
284 .send_command("Runtime.evaluate", Some(params), Some(&self.session_id))
285 .await?;
286
287 if let Some(exception) = result.exception_details {
288 return Err(PageError::EvaluationFailed(exception.text));
289 }
290
291 let object_id = result
292 .result
293 .object_id
294 .ok_or_else(|| PageError::EvaluationFailed("Result is not an object".to_string()))?;
295
296 Ok(JsHandle::new(
297 object_id,
298 self.session_id.clone(),
299 self.connection.clone(),
300 ))
301 }
302
303 async fn evaluate_internal<T: DeserializeOwned>(
305 &self,
306 expression: &str,
307 arg: Option<serde_json::Value>,
308 _timeout: Duration,
309 ) -> Result<T, PageError> {
310 if self.closed {
311 return Err(PageError::Closed);
312 }
313
314 trace!(expression = expression, "Evaluating JavaScript");
315
316 let final_expression = if let Some(arg_value) = arg {
318 let arg_json = serde_json::to_string(&arg_value)
320 .map_err(|e| PageError::EvaluationFailed(format!("Failed to serialize: {e}")))?;
321
322 format!("({expression})({arg_json})")
323 } else {
324 wrap_expression(expression)
325 };
326
327 let params = EvaluateParams {
328 expression: final_expression,
329 object_group: None,
330 include_command_line_api: None,
331 silent: Some(false),
332 context_id: None,
333 return_by_value: Some(true),
334 await_promise: Some(true),
335 };
336
337 let result: EvaluateResult = self
338 .connection
339 .send_command("Runtime.evaluate", Some(params), Some(&self.session_id))
340 .await?;
341
342 if let Some(exception) = result.exception_details {
343 return Err(PageError::EvaluationFailed(exception.text));
344 }
345
346 let value = result.result.value.unwrap_or(serde_json::Value::Null);
349
350 serde_json::from_value(value)
351 .map_err(|e| PageError::EvaluationFailed(format!("Failed to deserialize: {e}")))
352 }
353}
354
355pub(super) fn wrap_expression(expression: &str) -> String {
357 let trimmed = expression.trim();
358
359 if trimmed.starts_with("()")
361 || trimmed.starts_with("async ()")
362 || trimmed.starts_with("async()")
363 || trimmed.starts_with("function")
364 || trimmed.starts_with("async function")
365 {
366 format!("({trimmed})()")
367 } else {
368 trimmed.to_string()
369 }
370}