tauri_plugin_prevent_default/
lib.rs1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2#![doc = include_str!("../README.md")]
3#![allow(clippy::format_push_string)]
4
5mod display;
6mod error;
7mod shortcut;
8
9#[cfg(all(target_os = "windows", feature = "unstable-windows"))]
10mod platform;
11
12use bitflags::bitflags;
13use std::sync::Arc;
14use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
15use tauri::{Manager, Runtime};
16
17pub use error::Error;
18pub use shortcut::{
19 KeyboardShortcut, KeyboardShortcutBuilder, ModifierKey, PointerEvent, PointerShortcut,
20 PointerShortcutBuilder, Shortcut, ShortcutKind,
21};
22
23#[cfg(all(target_os = "windows", feature = "unstable-windows"))]
24pub use platform::windows::PlatformOptions;
25
26bitflags! {
27 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28 pub struct Flags: u32 {
29 const FIND = 1 << 0;
31 const CARET_BROWSING = 1 << 1;
33 const DEV_TOOLS = 1 << 2;
35 const DOWNLOADS = 1 << 3;
37 const FOCUS_MOVE = 1 << 4;
39 const RELOAD = 1 << 5;
41 const SOURCE = 1 << 6;
43 const OPEN = 1 << 7;
45 const PRINT = 1 << 8;
47 const CONTEXT_MENU = 1 << 9;
49 }
50}
51
52impl Flags {
53 pub fn keyboard() -> Self {
55 Self::all().difference(Self::pointer())
56 }
57
58 pub fn pointer() -> Self {
60 Self::CONTEXT_MENU
61 }
62
63 pub fn debug() -> Self {
65 if cfg!(debug_assertions) {
66 Self::all().difference(Self::CONTEXT_MENU | Self::DEV_TOOLS | Self::RELOAD)
67 } else {
68 Self::all()
69 }
70 }
71}
72
73impl Default for Flags {
74 fn default() -> Self {
75 Self::all()
76 }
77}
78
79pub struct Builder {
80 flags: Flags,
81 shortcuts: Vec<Box<dyn Shortcut>>,
82 check_origin: Option<String>,
83
84 #[cfg(all(target_os = "windows", feature = "unstable-windows"))]
85 platform: PlatformOptions,
86}
87
88#[allow(clippy::derivable_impls)]
89impl Default for Builder {
90 fn default() -> Self {
91 Self {
92 flags: Flags::default(),
93 shortcuts: Vec::new(),
94 check_origin: None,
95
96 #[cfg(all(target_os = "windows", feature = "unstable-windows"))]
97 platform: PlatformOptions::default(),
98 }
99 }
100}
101
102impl Builder {
103 pub fn new() -> Self {
105 Self::default()
106 }
107
108 #[must_use]
119 pub fn with_flags(mut self, flags: Flags) -> Self {
120 self.flags = flags;
121 self
122 }
123
124 #[must_use]
138 pub fn shortcut<S>(mut self, shortcut: S) -> Self
139 where
140 S: Shortcut + 'static,
141 {
142 self.shortcuts.push(Box::new(shortcut));
143 self
144 }
145
146 #[must_use]
148 pub fn check_origin(mut self, origin: impl AsRef<str>) -> Self {
149 self.check_origin = origin.as_ref().to_owned().into();
150 self
151 }
152
153 #[must_use]
155 #[cfg(all(target_os = "windows", feature = "unstable-windows"))]
156 pub fn platform(mut self, options: PlatformOptions) -> Self {
157 self.platform = options;
158 self
159 }
160
161 pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
163 let script = self.create_script();
164 self
165 .plugin_builder()
166 .js_init_script(script.into())
167 .build()
168 }
169
170 pub fn build_with_manual_injection<R: Runtime>(mut self) -> TauriPlugin<R> {
187 let script = self.create_script();
188 self
189 .plugin_builder()
190 .setup(move |app, _| {
191 app.manage(script);
192 Ok(())
193 })
194 .build()
195 }
196
197 #[allow(clippy::unused_self)]
198 fn plugin_builder<R: Runtime>(self) -> PluginBuilder<R> {
199 #[allow(unused_mut)]
200 let mut builder = PluginBuilder::new("prevent-default");
201
202 #[cfg(all(target_os = "windows", feature = "unstable-windows"))]
203 {
204 let options = self.platform;
205 builder = builder.on_webview_ready(move |webview| {
206 platform::windows::on_webview_ready(&webview, &options);
207 });
208 }
209
210 builder
211 }
212
213 fn create_script(&mut self) -> Script {
214 self.add_keyboard_shortcuts();
215 self.add_pointer_shortcuts();
216
217 let mut script = String::new();
218
219 for shortcut in &mut self.shortcuts {
220 match shortcut.kind() {
221 ShortcutKind::Keyboard(it) => {
222 let modifiers = it.modifiers();
223 let mut options = String::with_capacity(modifiers.len() * 12);
224 for modifier in modifiers {
225 options.push_str(&format!("{modifier}:true,"));
226 }
227
228 let options = options.trim_end_matches(',');
229 script.push_str(&format!("onKey('{}',{{{}}});", it.key(), options));
230 }
231 ShortcutKind::Pointer(it) => {
232 script.push_str(&format!("onPointer('{}');", it.event()));
233 }
234 }
235 }
236
237 let origin = self
238 .check_origin
239 .as_deref()
240 .map(|it| format!("const ORIGIN='{it}';"))
241 .unwrap_or_else(|| "const ORIGIN=null;".to_owned());
242
243 include_str!("../scripts/script.js")
244 .trim()
245 .replace("/*ORIGIN*/", &origin)
246 .replace("/*SCRIPT*/", &script)
247 .into()
248 }
249
250 fn add_keyboard_shortcuts(&mut self) {
251 use shortcut::ModifierKey::{CtrlKey, ShiftKey};
252
253 macro_rules! on_key {
254 ($($arg:literal)+) => {
255 $(
256 let shortcut = KeyboardShortcut::new($arg);
257 self.shortcuts.push(Box::new(shortcut));
258 )*
259 };
260 ($modifiers:expr, $($arg:literal),+) => {
261 $(
262 let shortcut = KeyboardShortcut::with_modifiers($arg, $modifiers);
263 self.shortcuts.push(Box::new(shortcut));
264 )*
265 };
266 }
267
268 if self.flags.contains(Flags::FIND) {
269 on_key!("F3");
270 on_key!(&[CtrlKey], "f", "g");
271 on_key!(&[CtrlKey, ShiftKey], "g");
272 }
273
274 if self.flags.contains(Flags::CARET_BROWSING) {
275 on_key!("F7");
276 }
277
278 if self.flags.contains(Flags::DEV_TOOLS) {
279 on_key!(&[CtrlKey, ShiftKey], "i");
280 }
281
282 if self.flags.contains(Flags::DOWNLOADS) {
283 on_key!(&[CtrlKey], "j");
284 }
285
286 if self.flags.contains(Flags::FOCUS_MOVE) {
287 on_key!(&[ShiftKey], "Tab");
288 }
289
290 if self.flags.contains(Flags::RELOAD) {
291 on_key!("F5");
292 on_key!(&[CtrlKey], "F5");
293 on_key!(&[ShiftKey], "F5");
294 on_key!(&[CtrlKey], "r");
295 on_key!(&[CtrlKey, ShiftKey], "r");
296 }
297
298 if self.flags.contains(Flags::SOURCE) {
299 on_key!(&[CtrlKey], "u");
300 }
301
302 if self.flags.contains(Flags::OPEN) {
303 on_key!(&[CtrlKey], "o");
304 }
305
306 if self.flags.contains(Flags::PRINT) {
307 on_key!(&[CtrlKey], "p");
308 on_key!(&[CtrlKey, ShiftKey], "p");
309 }
310 }
311
312 fn add_pointer_shortcuts(&mut self) {
313 if self.flags.contains(Flags::CONTEXT_MENU) {
314 let shortcut = PointerShortcut::new(PointerEvent::ContextMenu);
315 self.shortcuts.push(Box::new(shortcut));
316 }
317 }
318}
319
320pub trait PreventDefault<R: Runtime> {
322 fn script(&self) -> Script;
328
329 fn try_script(&self) -> Option<Script>;
334}
335
336impl<R, T> PreventDefault<R> for T
337where
338 R: Runtime,
339 T: Manager<R>,
340{
341 fn script(&self) -> Script {
342 (*self.app_handle().state::<Script>()).clone()
343 }
344
345 fn try_script(&self) -> Option<Script> {
346 self
347 .app_handle()
348 .try_state::<Script>()
349 .as_deref()
350 .cloned()
351 }
352}
353
354pub struct Script(Arc<str>);
356
357impl Clone for Script {
358 fn clone(&self) -> Self {
359 Self(Arc::clone(&self.0))
360 }
361}
362
363impl From<String> for Script {
364 fn from(value: String) -> Self {
365 Script(Arc::from(value))
366 }
367}
368
369impl From<Script> for String {
370 fn from(value: Script) -> Self {
371 String::from(value.0.as_ref())
372 }
373}
374
375pub fn init<R: Runtime>() -> TauriPlugin<R> {
377 Builder::default().build()
378}
379
380pub fn init_with_manual_injection<R: Runtime>() -> TauriPlugin<R> {
382 Builder::default().build_with_manual_injection()
383}
384
385pub fn with_flags<R: Runtime>(flags: Flags) -> TauriPlugin<R> {
387 Builder::new().with_flags(flags).build()
388}
389
390pub fn with_flags_and_manual_injection<R: Runtime>(flags: Flags) -> TauriPlugin<R> {
392 Builder::new()
393 .with_flags(flags)
394 .build_with_manual_injection()
395}
396
397pub fn debug<R: Runtime>() -> TauriPlugin<R> {
399 Builder::new().with_flags(Flags::debug()).build()
400}
401
402pub fn debug_with_manual_injection<R: Runtime>() -> TauriPlugin<R> {
405 Builder::new()
406 .with_flags(Flags::debug())
407 .build_with_manual_injection()
408}