glass/browser/session/
clipboard.rs1use super::*;
7
8const CLIPBOARD_MAX_BYTES: usize = 8192;
10
11impl BrowserSession {
12 pub async fn clipboard_read(&self) -> BrowserResult<String> {
18 self.cdp.with_current_route(async {
19 let _ = self.cdp.send("Browser.grantPermissions", Some(serde_json::json!({
20 "permissions": ["clipboardReadWrite"], "origin": "*"
21 }))).await;
22 let expr = format!(
23 "navigator.clipboard.readText().then(t => t.slice(0,{CLIPBOARD_MAX_BYTES})).catch(() => '')"
24 );
25 let raw = self.cdp.evaluate(&expr).await?;
26 let value = runtime_value(&raw)?;
27 Ok(value.as_str().unwrap_or("").to_string())
28 }).await
29 }
30
31 pub async fn clipboard_write(&self, text: &str) -> BrowserResult<()> {
37 let bounded = if text.len() > CLIPBOARD_MAX_BYTES {
38 let mut end = CLIPBOARD_MAX_BYTES;
39 while end > 0 && !text.is_char_boundary(end) {
40 end -= 1;
41 }
42 &text[..end]
43 } else {
44 text
45 };
46 self.cdp
47 .with_current_route(async {
48 let _ = self
49 .cdp
50 .send(
51 "Browser.grantPermissions",
52 Some(serde_json::json!({
53 "permissions": ["clipboardReadWrite"], "origin": "*"
54 })),
55 )
56 .await;
57 let escaped = bounded
58 .replace('\\', "\\\\")
59 .replace('\'', "\\'")
60 .replace('\n', "\\n")
61 .replace('\r', "\\r")
62 .replace('\u{2028}', "\\u2028")
63 .replace('\u{2029}', "\\u2029");
64 let expr = format!(
65 "navigator.clipboard.writeText('{escaped}').then(() => true).catch(() => false)"
66 );
67 self.cdp.evaluate(&expr).await?;
68 Ok(())
69 })
70 .await
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn clipboard_max_bytes_is_8k() {
80 assert_eq!(CLIPBOARD_MAX_BYTES, 8192);
82 }
83
84 #[test]
85 fn clipboard_max_bytes_is_power_of_two() {
86 assert_eq!(CLIPBOARD_MAX_BYTES, 1 << 13);
88 }
89
90 #[test]
91 fn clipboard_max_bytes_is_reasonable_upper_bound() {
92 const { assert!(CLIPBOARD_MAX_BYTES <= 16384) };
94 const { assert!(CLIPBOARD_MAX_BYTES >= 4096) };
96 }
97}