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
78mod fallback_clipboard {
79    use std::sync::{LockResult, Mutex, OnceLock};
80
81    use crate::clipboard_delegate::StringRequest;
82
83    /// If the clipboard cannot be accessed, we fall back to a simple `String` to store
84    /// text for the clipboard. This obviously does not work across processes.
85    static SHARED_FALLBACK_CLIPBOARD: OnceLock<Mutex<String>> = OnceLock::new();
86
87    fn with_shared_clipboard(callback: impl FnOnce(&mut String)) {
88        let clipboard_mutex =
89            SHARED_FALLBACK_CLIPBOARD.get_or_init(|| Mutex::new(Default::default()));
90        if let LockResult::Ok(mut string) = clipboard_mutex.lock() {
91            callback(&mut string)
92        }
93    }
94
95    pub(super) fn clear() {
96        with_shared_clipboard(|clipboard_string| {
97            clipboard_string.clear();
98        });
99    }
100
101    pub(super) fn get_text(request: StringRequest) {
102        with_shared_clipboard(move |clipboard_string| request.success(clipboard_string.clone()));
103    }
104
105    pub(super) fn set_text(new_contents: String) {
106        with_shared_clipboard(move |clipboard_string| {
107            *clipboard_string = new_contents;
108        });
109    }
110}
111
112#[cfg(all(
113    feature = "clipboard",
114    not(any(target_os = "android", target_env = "ohos"))
115))]
116mod clipboard {
117    use std::sync::OnceLock;
118
119    use arboard::Clipboard;
120    use parking_lot::Mutex;
121
122    use super::StringRequest;
123    use crate::clipboard_delegate::fallback_clipboard;
124
125    /// A shared clipboard for use by the [`DefaultClipboardDelegate`]. This is protected by
126    /// a mutex so that it can only be used by one thread at a time. The `arboard` documentation
127    /// suggests that more than one thread shouldn't try to access the Windows clipboard at a
128    /// time. See <https://docs.rs/arboard/latest/arboard/struct.Clipboard.html>.
129    static SHARED_CLIPBOARD: OnceLock<Option<Mutex<Clipboard>>> = OnceLock::new();
130
131    fn with_shared_clipboard<ResultType>(
132        callback: impl FnOnce(&mut Clipboard) -> Result<ResultType, arboard::Error>,
133    ) -> Result<ResultType, arboard::Error> {
134        match SHARED_CLIPBOARD.get_or_init(|| Clipboard::new().ok().map(Mutex::new)) {
135            Some(clipboard_mutex) => callback(&mut clipboard_mutex.lock()),
136            None => Err(arboard::Error::ClipboardNotSupported),
137        }
138    }
139
140    pub(super) fn clear() {
141        if with_shared_clipboard(|clipboard| clipboard.clear()).is_err() {
142            fallback_clipboard::clear();
143        }
144    }
145
146    pub(super) fn get_text(request: StringRequest) {
147        if let Ok(text) = with_shared_clipboard(|clipboard| clipboard.get_text()) {
148            request.success(text);
149            return;
150        };
151        fallback_clipboard::get_text(request);
152    }
153
154    pub(super) fn set_text(new_contents: String) {
155        if with_shared_clipboard(|clipboard| clipboard.set_text(&new_contents)).is_err() {
156            fallback_clipboard::set_text(new_contents);
157        }
158    }
159}
160
161#[cfg(any(not(feature = "clipboard"), target_os = "android", target_env = "ohos"))]
162mod clipboard {
163    use super::StringRequest;
164    use crate::clipboard_delegate::fallback_clipboard;
165
166    pub(super) fn clear() {
167        fallback_clipboard::clear();
168    }
169
170    pub(super) fn get_text(request: StringRequest) {
171        fallback_clipboard::get_text(request);
172    }
173
174    pub(super) fn set_text(new_contents: String) {
175        fallback_clipboard::set_text(new_contents);
176    }
177}