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