tauri_plugin_prevent_default/
lib.rs1#![cfg_attr(docsrs, feature(doc_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 let origin = origin.as_ref().trim();
151 if !origin.is_empty() {
152 self.check_origin = Some(origin.to_owned());
153 }
154
155 self
156 }
157
158 #[must_use]
160 #[cfg(all(target_os = "windows", feature = "platform-windows"))]
161 pub fn platform(mut self, options: PlatformOptions) -> Self {
162 self.platform = options;
163 self
164 }
165
166 pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
168 let script = self.create_script();
169 self
170 .plugin_builder()
171 .js_init_script(script.to_string())
172 .build()
173 }
174
175 pub fn build_with_manual_injection<R: Runtime>(mut self) -> TauriPlugin<R> {
192 let script = self.create_script();
193 self
194 .plugin_builder()
195 .setup(move |app, _| {
196 app.manage(script);
197 Ok(())
198 })
199 .build()
200 }
201
202 #[allow(clippy::unused_self)]
203 fn plugin_builder<R: Runtime>(self) -> PluginBuilder<R> {
204 #[allow(unused_mut)]
205 let mut builder = PluginBuilder::new("prevent-default");
206
207 #[cfg(all(target_os = "windows", feature = "platform-windows"))]
208 {
209 let options = self.platform;
210 builder = builder.on_webview_ready(move |webview| {
211 platform::windows::on_webview_ready(&webview, options.clone());
212 });
213 }
214
215 builder
216 }
217
218 fn create_script(&mut self) -> Script {
219 self.add_keyboard_shortcuts();
220 self.add_pointer_shortcuts();
221
222 let mut script = String::new();
223
224 for shortcut in &mut self.shortcuts {
225 match shortcut.kind() {
226 ShortcutKind::Keyboard(it) => {
227 let modifiers = it.modifiers();
228 let mut options = String::with_capacity(modifiers.len() * 12);
229 for modifier in modifiers {
230 options.push_str(&format!("{modifier}:true,"));
231 }
232
233 let options = options.trim_end_matches(',');
234 script.push_str(&format!("onKey('{}',{{{}}});", it.key(), options));
235 }
236 ShortcutKind::Pointer(it) => {
237 script.push_str(&format!("onPointer('{}');", it.event()));
238 }
239 }
240 }
241
242 let origin = self
243 .check_origin
244 .as_deref()
245 .map(|it| format!("const ORIGIN='{it}';"))
246 .unwrap_or_else(|| "const ORIGIN=null;".to_owned());
247
248 include_str!("../assets/script.js")
249 .trim()
250 .replace("/*ORIGIN*/", &origin)
251 .replace("/*SCRIPT*/", &script)
252 .into()
253 }
254
255 fn add_keyboard_shortcuts(&mut self) {
256 use shortcut::ModifierKey::{CtrlKey, ShiftKey};
257
258 macro_rules! on_key {
259 ($($arg:literal)+) => {
260 $(
261 let shortcut = KeyboardShortcut::new($arg);
262 self.shortcuts.push(Box::new(shortcut));
263 )*
264 };
265 ($modifiers:expr, $($arg:literal),+) => {
266 $(
267 let shortcut = KeyboardShortcut::with_modifiers($arg, $modifiers);
268 self.shortcuts.push(Box::new(shortcut));
269 )*
270 };
271 }
272
273 if self.flags.contains(Flags::FIND) {
274 on_key!("F3");
275 on_key!(&[CtrlKey], "f", "g");
276 on_key!(&[CtrlKey, ShiftKey], "g");
277 }
278
279 if self.flags.contains(Flags::CARET_BROWSING) {
280 on_key!("F7");
281 }
282
283 if self.flags.contains(Flags::DEV_TOOLS) {
284 on_key!(&[CtrlKey, ShiftKey], "i");
285 }
286
287 if self.flags.contains(Flags::DOWNLOADS) {
288 on_key!(&[CtrlKey], "j");
289 }
290
291 if self.flags.contains(Flags::FOCUS_MOVE) {
292 on_key!(&[ShiftKey], "Tab");
293 }
294
295 if self.flags.contains(Flags::RELOAD) {
296 on_key!("F5");
297 on_key!(&[CtrlKey], "F5");
298 on_key!(&[ShiftKey], "F5");
299 on_key!(&[CtrlKey], "r");
300 on_key!(&[CtrlKey, ShiftKey], "r");
301 }
302
303 if self.flags.contains(Flags::SOURCE) {
304 on_key!(&[CtrlKey], "u");
305 }
306
307 if self.flags.contains(Flags::OPEN) {
308 on_key!(&[CtrlKey], "o");
309 }
310
311 if self.flags.contains(Flags::PRINT) {
312 on_key!(&[CtrlKey], "p");
313 on_key!(&[CtrlKey, ShiftKey], "p");
314 }
315 }
316
317 fn add_pointer_shortcuts(&mut self) {
318 if self.flags.contains(Flags::CONTEXT_MENU) {
319 let shortcut = PointerShortcut::new(PointerEvent::ContextMenu);
320 self.shortcuts.push(Box::new(shortcut));
321 }
322 }
323}
324
325pub trait PreventDefault<R: Runtime> {
327 fn prevent_default_script(&self) -> Script;
333
334 fn try_prevent_default_script(&self) -> Option<Script>;
339}
340
341impl<R, T> PreventDefault<R> for T
342where
343 R: Runtime,
344 T: Manager<R>,
345{
346 fn prevent_default_script(&self) -> Script {
347 (*self.app_handle().state::<Script>()).clone()
348 }
349
350 fn try_prevent_default_script(&self) -> Option<Script> {
351 self
352 .app_handle()
353 .try_state::<Script>()
354 .as_deref()
355 .cloned()
356 }
357}
358
359pub fn init<R: Runtime>() -> TauriPlugin<R> {
361 Builder::default().build()
362}
363
364pub fn init_with_manual_injection<R: Runtime>() -> TauriPlugin<R> {
366 Builder::default().build_with_manual_injection()
367}
368
369pub fn with_flags<R: Runtime>(flags: Flags) -> TauriPlugin<R> {
371 Builder::new().with_flags(flags).build()
372}
373
374pub fn with_flags_and_manual_injection<R: Runtime>(flags: Flags) -> TauriPlugin<R> {
376 Builder::new()
377 .with_flags(flags)
378 .build_with_manual_injection()
379}
380
381pub fn debug<R: Runtime>() -> TauriPlugin<R> {
383 Builder::new().with_flags(Flags::debug()).build()
384}
385
386pub fn debug_with_manual_injection<R: Runtime>() -> TauriPlugin<R> {
389 Builder::new()
390 .with_flags(Flags::debug())
391 .build_with_manual_injection()
392}