cdp_html_shot/
element.rs

1use serde_json::json;
2use anyhow::{Context, Result};
3
4use crate::tab::Tab;
5use crate::general_utils;
6use crate::general_utils::next_id;
7
8/// Represents screenshot configuration parameters.
9#[derive(Debug)]
10struct ScreenshotConfig {
11    format: &'static str,
12    quality: Option<u8>,
13}
14
15impl Default for ScreenshotConfig {
16    fn default() -> Self {
17        Self {
18            format: "png",
19            quality: None,
20        }
21    }
22}
23
24/// An element instance.
25pub struct Element<'a> {
26    parent: &'a Tab,
27    // node_id: u64,
28    // value: String,
29    // tag_name: String,
30    backend_node_id: u64,
31    // attributes: Vec<String>,
32    // remote_object_id: String,
33}
34
35impl<'a> Element<'a> {
36    pub(crate) async fn new(parent: &'a Tab, node_id: u64) -> Result<Self> {
37        let msg_id = next_id();
38        let msg = json!({
39            "id": msg_id,
40            "method": "DOM.describeNode",
41            "params": {
42                "nodeId": node_id,
43                "depth": 100
44            }
45        }).to_string();
46
47        let res = general_utils::send_and_get_msg(parent.transport.clone(), msg_id, &parent.session_id, msg).await?;
48
49        let msg = general_utils::serde_msg(&res);
50
51        let node = msg["result"]
52            .get("node")
53            .context("Failed to get node")?;
54
55        // let attributes = node
56        //     .get("attributes")
57        //     .context("Failed to get attributes")?
58        //     .to_string();
59
60        // let attributes: Vec<String> = serde_json::from_str(&attributes)?;
61
62        // let tag_name = node
63        //     .get("nodeName")
64        //     .context("Failed to get nodeName")?
65        //     .as_str()
66        //     .context("Failed to convert nodeName to string")?
67        //     .to_string();
68
69        let backend_node_id = node
70            .get("backendNodeId")
71            .context("Failed to get backendNodeId")?
72            .as_u64()
73            .context("Failed to convert backendNodeId to u64")?;
74
75        // let msg_id = next_id();
76        // let msg = json!({
77        //     "id": msg_id,
78        //     "method": "DOM.resolveNode",
79        //     "params": {
80        //         "backendNodeId": backend_node_id,
81        //     }
82        // }).to_string();
83
84        // let res = general_utils::send_and_get_msg(parent.transport.clone(), msg_id, &parent.session_id, msg).await?;
85
86        // let msg = general_utils::serde_msg(&res);
87        // let object = msg["result"]
88        //     .get("object")
89        //     .context("Failed to get an object")?;
90
91        // let value = object
92        //     .get("value")
93        //     .unwrap_or(&json!(""))
94        //     .to_string();
95
96        // let remote_object_id = object
97        //     .get("objectId")
98        //     .context("Failed to get objectId")?
99        //     .as_str()
100        //     .context("Failed to convert objectId to string")?
101        //     .to_string();
102
103        Ok(Self {
104            parent,
105            // value,
106            // node_id,
107            // tag_name,
108            // attributes,
109            backend_node_id,
110            // remote_object_id,
111        })
112    }
113
114    /// Get the box model dimensions for an element.
115    async fn get_box_model_dimensions(&self) -> Result<(f64, f64, f64, f64)> {
116        let msg_id = next_id();
117        let msg = json!({
118            "id": msg_id,
119            "method": "DOM.getBoxModel",
120            "params": {
121                "backendNodeId": self.backend_node_id
122            }
123        }).to_string();
124
125        let res = general_utils::send_and_get_msg(
126            self.parent.transport.clone(),
127            msg_id,
128            &self.parent.session_id,
129            msg
130        ).await?;
131
132        let msg = general_utils::serde_msg(&res);
133        let model = msg["result"]
134            .get("model")
135            .context("Failed to get model")?;
136
137        Ok((
138            model["border"][0].as_f64().unwrap(), // top_left_x
139            model["border"][1].as_f64().unwrap(), // top_left_y
140            model["border"][2].as_f64().unwrap(), // top_right_x
141            model["border"][5].as_f64().unwrap()  // bottom_left_y
142        ))
143    }
144
145    /// Take a screenshot with the given configuration.
146    async fn take_screenshot_with_config(&self, config: ScreenshotConfig) -> Result<String> {
147        let (top_left_x, top_left_y, top_right_x, bottom_left_y) =
148            self.get_box_model_dimensions().await?;
149
150        let mut params = json!({
151            "format": config.format,
152            "clip": {
153                "x": top_left_x,
154                "y": top_left_y,
155                "width": top_right_x - top_left_x,
156                "height": bottom_left_y - top_left_y,
157                "scale": 1.0
158            },
159            "fromSurface": true,
160            "captureBeyondViewport": true,
161        });
162
163        if config.format == "jpeg" {
164            if let Some(quality) = config.quality {
165                params["quality"] = json!(quality);
166            }
167        }
168
169        let msg_id = next_id();
170        let msg = json!({
171            "id": msg_id,
172            "method": "Page.captureScreenshot",
173            "params": params
174        }).to_string();
175
176        self.parent.activate().await?;
177        let res = general_utils::send_and_get_msg(
178            self.parent.transport.clone(),
179            msg_id,
180            &self.parent.session_id,
181            msg
182        ).await?;
183
184        let msg = general_utils::serde_msg(&res);
185        let base64 = msg["result"]
186            .get("data")
187            .context("Failed to get data")?
188            .as_str()
189            .context("Failed to convert data to string")?
190            .to_string();
191
192        Ok(base64)
193    }
194
195    /// Capture a screenshot of the element in JPEG format.
196    pub async fn screenshot(&self) -> Result<String> {
197        self.take_screenshot_with_config(ScreenshotConfig {
198            format: "jpeg",
199            quality: Some(90),
200        }).await
201    }
202
203    /// Capture a raw screenshot of the element in PNG format.
204    pub async fn raw_screenshot(&self) -> Result<String> {
205        self.take_screenshot_with_config(ScreenshotConfig::default()).await
206    }
207}