Skip to main content

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 servo_base::generic_channel::GenericCallback;
6
7use crate::WebView;
8
9pub struct StringRequest {
10    pub(crate) result_sender: GenericCallback<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<GenericCallback<Result<String, String>>> for StringRequest {
27    fn from(result_sender: GenericCallback<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`](super::DefaultClipboardDelegate).
126    /// This is protected by a mutex so that it can only be used by one thread at a time.
127    /// The `arboard` documentation suggests that more than one thread shouldn't try to access
128    /// the Windows clipboard at a 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(all(feature = "clipboard", target_env = "ohos"))]
162mod clipboard {
163    use super::StringRequest;
164    use crate::clipboard_delegate::fallback_clipboard;
165
166    pub(super) fn clear() {
167        if let Err(error) = ohos_pasteboard::clear() {
168            log::warn!(
169                "OHOS pasteboard clear failed ({error}); using in-memory fallback_clipboard"
170            );
171            fallback_clipboard::clear();
172        }
173    }
174
175    pub(super) fn get_text(request: StringRequest) {
176        match ohos_pasteboard::get_text() {
177            Ok(text) => request.success(text),
178            Err(ohos_pasteboard::Error::NoText) => request.success(String::new()),
179            Err(error) => {
180                log::warn!(
181                    "OHOS pasteboard get_text failed ({error}); using in-memory fallback_clipboard"
182                );
183                fallback_clipboard::get_text(request);
184            },
185        }
186    }
187
188    pub(super) fn set_text(new_contents: String) {
189        if let Err(error) = ohos_pasteboard::set_text(&new_contents) {
190            log::warn!(
191                "OHOS pasteboard set_text failed ({error}); using in-memory fallback_clipboard"
192            );
193            fallback_clipboard::set_text(new_contents);
194        }
195    }
196}
197
198#[cfg(any(
199    not(feature = "clipboard"),
200    all(feature = "clipboard", target_os = "android")
201))]
202mod clipboard {
203    use super::StringRequest;
204    use crate::clipboard_delegate::fallback_clipboard;
205
206    pub(super) fn clear() {
207        fallback_clipboard::clear();
208    }
209
210    pub(super) fn get_text(request: StringRequest) {
211        fallback_clipboard::get_text(request);
212    }
213
214    pub(super) fn set_text(new_contents: String) {
215        fallback_clipboard::set_text(new_contents);
216    }
217}