cdp_html_shot/
element.rs

1use crate::tab::Tab;
2use crate::transport::next_id;
3use crate::types::{CaptureOptions, ImageFormat};
4use crate::utils::{self, send_and_get_msg};
5use anyhow::{Context, Result};
6use serde_json::json;
7
8/// Represents a DOM element controlled via CDP.
9pub struct Element<'a> {
10    parent: &'a Tab,
11    backend_node_id: u64,
12}
13
14impl<'a> Element<'a> {
15    pub(crate) async fn new(parent: &'a Tab, node_id: u64) -> Result<Self> {
16        let msg_id = next_id();
17        let msg = json!({
18            "id": msg_id,
19            "method": "DOM.describeNode",
20            "params": { "nodeId": node_id, "depth": 100 }
21        })
22        .to_string();
23
24        let res =
25            send_and_get_msg(parent.transport.clone(), msg_id, &parent.session_id, msg).await?;
26        let data = utils::serde_msg(&res)?;
27        let backend_node_id = data["result"]["node"]["backendNodeId"]
28            .as_u64()
29            .context("Missing backendNodeId")?;
30
31        Ok(Self {
32            parent,
33            backend_node_id,
34        })
35    }
36
37    pub async fn screenshot(&self) -> Result<String> {
38        self.screenshot_with_options(CaptureOptions::new().with_quality(90))
39            .await
40    }
41
42    pub async fn raw_screenshot(&self) -> Result<String> {
43        self.screenshot_with_options(CaptureOptions::raw_png())
44            .await
45    }
46
47    pub async fn screenshot_with_options(&self, opts: CaptureOptions) -> Result<String> {
48        if let Some(ref viewport) = opts.viewport {
49            self.parent.set_viewport(viewport).await?;
50        }
51
52        let msg_id = next_id();
53        let msg_box = json!({
54            "id": msg_id,
55            "method": "DOM.getBoxModel",
56            "params": { "backendNodeId": self.backend_node_id }
57        })
58        .to_string();
59
60        let res_box = send_and_get_msg(
61            self.parent.transport.clone(),
62            msg_id,
63            &self.parent.session_id,
64            msg_box,
65        )
66        .await?;
67        let data_box = utils::serde_msg(&res_box)?;
68        let border = &data_box["result"]["model"]["border"];
69
70        let (x, y, w, h) = (
71            border[0].as_f64().unwrap_or(0.0),
72            border[1].as_f64().unwrap_or(0.0),
73            (border[2].as_f64().unwrap_or(0.0) - border[0].as_f64().unwrap_or(0.0)),
74            (border[5].as_f64().unwrap_or(0.0) - border[1].as_f64().unwrap_or(0.0)),
75        );
76
77        let mut params = json!({
78            "format": opts.format.as_str(),
79            "clip": { "x": x, "y": y, "width": w, "height": h, "scale": 1.0 },
80            "fromSurface": true,
81            "captureBeyondViewport": opts.full_page,
82        });
83
84        if matches!(opts.format, ImageFormat::Jpeg | ImageFormat::WebP) {
85            params["quality"] = json!(opts.quality.unwrap_or(90));
86        }
87
88        if opts.omit_background && matches!(opts.format, ImageFormat::Png) {
89            let msg_id = next_id();
90            let msg = json!({
91                "id": msg_id,
92                "method": "Emulation.setDefaultBackgroundColorOverride",
93                "params": { "color": { "r": 0, "g": 0, "b": 0, "a": 0 } }
94            })
95            .to_string();
96            send_and_get_msg(
97                self.parent.transport.clone(),
98                msg_id,
99                &self.parent.session_id,
100                msg,
101            )
102            .await?;
103        }
104
105        let msg_id = next_id();
106        let msg_cap = json!({
107            "id": msg_id,
108            "method": "Page.captureScreenshot",
109            "params": params
110        })
111        .to_string();
112
113        self.parent.activate().await?;
114        let res_cap = send_and_get_msg(
115            self.parent.transport.clone(),
116            msg_id,
117            &self.parent.session_id,
118            msg_cap,
119        )
120        .await?;
121        let data_cap = utils::serde_msg(&res_cap)?;
122
123        if opts.omit_background && matches!(opts.format, ImageFormat::Png) {
124            let msg_id = next_id();
125            let msg = json!({
126                "id": msg_id,
127                "method": "Emulation.setDefaultBackgroundColorOverride",
128                "params": {}
129            })
130            .to_string();
131            let _ = send_and_get_msg(
132                self.parent.transport.clone(),
133                msg_id,
134                &self.parent.session_id,
135                msg,
136            )
137            .await;
138        }
139
140        data_cap["result"]["data"]
141            .as_str()
142            .map(|s| s.to_string())
143            .context("No image data received")
144    }
145
146    pub fn backend_node_id(&self) -> u64 {
147        self.backend_node_id
148    }
149}