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