servo/
clipboard_delegate.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use ipc_channel::ipc::IpcSender;
6
7use crate::WebView;
8
9pub struct StringRequest {
10    pub(crate) result_sender: IpcSender<Result<String, String>>,
11    response_sent: bool,
12}
13
14impl StringRequest {
15    pub fn success(mut self, string: String) {
16        let _ = self.result_sender.send(Ok(string));
17        self.response_sent = true;
18    }
19
20    pub fn failure(mut self, message: String) {
21        let _ = self.result_sender.send(Err(message));
22        self.response_sent = true;
23    }
24}
25
26impl From<IpcSender<Result<String, String>>> for StringRequest {
27    fn from(result_sender: IpcSender<Result<String, String>>) -> Self {
28        Self {
29            result_sender,
30            response_sent: false,
31        }
32    }
33}
34
35impl Drop for StringRequest {
36    fn drop(&mut self) {
37        if !self.response_sent {
38            let _ = self
39                .result_sender
40                .send(Err("No response sent to request.".into()));
41        }
42    }
43}
44
45/// A delegate that is responsible for accessing the system clipboard. On Mac, Windows, and
46/// Linux if the `clipboard` feature is enabled, a default delegate is automatically used
47/// that implements clipboard support. An embedding application can override this delegate
48/// by using this trait.
49pub trait ClipboardDelegate {
50    /// A request to clear all contents of the system clipboard.
51    fn clear(&self, _webview: WebView) {}
52
53    /// A request to get the text contents of the system clipboard. Once the contents are
54    /// retrieved the embedder should call [`StringRequest::success`] with the text or
55    /// [`StringRequest::failure`] with a failure message.
56    fn get_text(&self, _webview: WebView, _request: StringRequest) {}
57
58    /// A request to set the text contents of the system clipboard to `new_contents`.
59    fn set_text(&self, _webview: WebView, _new_contents: String) {}
60}
61
62pub(crate) struct DefaultClipboardDelegate;
63
64impl ClipboardDelegate for DefaultClipboardDelegate {
65    fn clear(&self, _webview: WebView) {
66        clipboard::clear();
67    }
68
69    fn get_text(&self, _webview: WebView, request: StringRequest) {
70        clipboard::get_text(request);
71    }
72
73    fn set_text(&self, _webview: WebView, new_contents: String) {
74        clipboard::set_text(new_contents);
75    }
76}
77
78#[cfg(all(
79    feature = "clipboard",
80    not(any(target_os = "android", target_env = "ohos"))
81))]
82mod clipboard {
83    use std::sync::OnceLock;
84
85    use arboard::Clipboard;
86    use parking_lot::Mutex;
87
88    use super::StringRequest;
89
90    /// A shared clipboard for use by the [`DefaultClipboardDelegate`]. This is protected by
91    /// a mutex so that it can only be used by one thread at a time. The `arboard` documentation
92    /// suggests that more than one thread shouldn't try to access the Windows clipboard at a
93    /// time. See <https://docs.rs/arboard/latest/arboard/struct.Clipboard.html>.
94    static SHARED_CLIPBOARD: OnceLock<Option<Mutex<Clipboard>>> = OnceLock::new();
95
96    fn with_shared_clipboard(callback: impl FnOnce(&mut Clipboard)) {
97        if let Some(clipboard_mutex) =
98            SHARED_CLIPBOARD.get_or_init(|| Clipboard::new().ok().map(Mutex::new))
99        {
100            callback(&mut clipboard_mutex.lock())
101        }
102    }
103
104    pub(super) fn clear() {
105        with_shared_clipboard(|clipboard| {
106            let _ = clipboard.clear();
107        });
108    }
109
110    pub(super) fn get_text(request: StringRequest) {
111        with_shared_clipboard(move |clipboard| match clipboard.get_text() {
112            Ok(text) => request.success(text),
113            Err(error) => request.failure(format!("{error:?}")),
114        });
115    }
116
117    pub(super) fn set_text(new_contents: String) {
118        with_shared_clipboard(move |clipboard| {
119            let _ = clipboard.set_text(new_contents);
120        });
121    }
122}
123
124#[cfg(any(not(feature = "clipboard"), target_os = "android", target_env = "ohos"))]
125mod clipboard {
126    use super::StringRequest;
127
128    pub(super) fn clear() {}
129    pub(super) fn get_text(_: StringRequest) {}
130    pub(super) fn set_text(_: String) {}
131}