From df37d6fb21ae83dd4125823806d3ecf7d36296b4 Mon Sep 17 00:00:00 2001 From: F0RLE Date: Wed, 8 Jul 2026 21:22:43 +0300 Subject: [PATCH 1/7] feat: expose wry permission handler API in tauri --- .changes/permission-handler.md | 11 + Cargo.lock | 222 +++++++++++++++------ crates/tauri-runtime-wry/Cargo.toml | 2 +- crates/tauri-runtime-wry/src/lib.rs | 42 ++++ crates/tauri-runtime/src/webview.rs | 96 +++++++++ crates/tauri/src/app.rs | 23 +++ crates/tauri/src/ipc/protocol.rs | 2 + crates/tauri/src/manager/mod.rs | 3 + crates/tauri/src/manager/webview.rs | 33 +++ crates/tauri/src/plugin.rs | 70 +++++++ crates/tauri/src/webview/mod.rs | 67 ++++++- crates/tauri/src/webview/webview_window.rs | 14 ++ 12 files changed, 518 insertions(+), 67 deletions(-) create mode 100644 .changes/permission-handler.md diff --git a/.changes/permission-handler.md b/.changes/permission-handler.md new file mode 100644 index 000000000000..7a443d104e16 --- /dev/null +++ b/.changes/permission-handler.md @@ -0,0 +1,11 @@ +--- +"tauri": "patch:feat" +"tauri-runtime": "patch:feat" +"tauri-runtime-wry": "patch:feat" +--- + +Expose the `wry` permission handler API through Tauri. +This includes support for permission types such as `DisplayCapture`, `Midi`, `Sensors`, `MediaKeySystemAccess`, `LocalFonts`, `WindowManagement`, `PointerLock`, `AutomaticDownloads`, `FileSystemAccess`, and `Autoplay`. +Added `PermissionResponse::{Allow, Deny, Default}` for runtime permission decisions. +Added Android support for geolocation, microphone, camera, protected media, and MIDI requests via JNI. +Updated Linux DisplayCapture support for WebKitGTK versions older than 2.42. diff --git a/Cargo.lock b/Cargo.lock index 8f2823918ae4..2d678d9d7431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1370,7 +1370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -1379,7 +1379,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -1729,7 +1729,7 @@ version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ - "cssparser-macros", + "cssparser-macros 0.6.1", "dtoa-short", "itoa", "matches", @@ -1746,7 +1746,20 @@ version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" dependencies = [ - "cssparser-macros", + "cssparser-macros 0.6.1", + "dtoa-short", + "itoa", + "phf 0.13.1", + "smallvec", +] + +[[package]] +name = "cssparser" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" +dependencies = [ + "cssparser-macros 0.7.0", "dtoa-short", "itoa", "phf 0.13.1", @@ -1763,6 +1776,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "cssparser-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a2a99df6e410a8ff4245aa2006499ea662245f967cc7c0a38c83ef8eb44dbf" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "ct-codecs" version = "1.1.7" @@ -2119,7 +2142,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.0", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2183,6 +2206,21 @@ dependencies = [ "tendril 0.5.0", ] +[[package]] +name = "dom_query" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc" +dependencies = [ + "bit-set", + "cssparser 0.37.0", + "foldhash 0.2.0", + "html5ever 0.39.0", + "precomputed-hash", + "selectors 0.38.0", + "tendril 0.5.0", +] + [[package]] name = "dpi" version = "0.1.1" @@ -2503,7 +2541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3434,6 +3472,16 @@ dependencies = [ "markup5ever 0.38.0", ] +[[package]] +name = "html5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" +dependencies = [ + "log", + "markup5ever 0.39.0", +] + [[package]] name = "http" version = "1.3.1" @@ -3832,7 +3880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.15.5", "serde", "serde_core", ] @@ -4580,6 +4628,17 @@ dependencies = [ "web_atoms", ] +[[package]] +name = "markup5ever" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" +dependencies = [ + "log", + "tendril 0.5.0", + "web_atoms", +] + [[package]] name = "match_token" version = "0.1.0" @@ -4755,12 +4814,12 @@ dependencies = [ "objc2 0.6.4", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", "once_cell", "png 0.18.1", "serde", "thiserror 2.0.12", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5180,55 +5239,57 @@ dependencies = [ [[package]] name = "objc2-app-kit" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-core-foundation", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-cloud-kit" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ "bitflags 2.13.0", "objc2 0.6.4", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-core-data" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" dependencies = [ "objc2 0.6.4", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-core-foundation" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ "bitflags 2.13.0", + "dispatch2", "objc2 0.6.4", ] [[package]] name = "objc2-core-graphics" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ "bitflags 2.13.0", + "dispatch2", "objc2 0.6.4", "objc2-core-foundation", "objc2-io-surface", @@ -5236,22 +5297,34 @@ dependencies = [ [[package]] name = "objc2-core-image" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" dependencies = [ "objc2 0.6.4", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-core-location" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d31f4c5b5192304996badc466aeadffe1411d73a9bbd3b18b6b2ee9d048b07bd" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" dependencies = [ "objc2 0.6.4", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.0", + "objc2 0.6.4", + "objc2-core-foundation", + "objc2-core-graphics", ] [[package]] @@ -5283,9 +5356,9 @@ dependencies = [ [[package]] name = "objc2-foundation" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.0", "block2 0.6.2", @@ -5295,9 +5368,9 @@ dependencies = [ [[package]] name = "objc2-io-surface" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ "bitflags 2.13.0", "objc2 0.6.4", @@ -5331,21 +5404,21 @@ dependencies = [ [[package]] name = "objc2-quartz-core" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ "bitflags 2.13.0", "objc2 0.6.4", "objc2-core-foundation", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-ui-kit" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777a571be14a42a3990d4ebedaeb8b54cd17377ec21b92e8200ac03797b3bee1" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ "bitflags 2.13.0", "block2 0.6.2", @@ -5356,33 +5429,34 @@ dependencies = [ "objc2-core-graphics", "objc2-core-image", "objc2-core-location", - "objc2-foundation 0.3.0", - "objc2-quartz-core 0.3.0", + "objc2-core-text", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", "objc2-user-notifications", ] [[package]] name = "objc2-user-notifications" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670fe793adbf3b5e93686d48a05a7ed7ee53dfa65d106ced4805fae8969059b2" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" dependencies = [ "objc2 0.6.4", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", ] [[package]] name = "objc2-web-kit" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b717127e4014b0f9f3e8bba3d3f2acec81f1bde01f656823036e823ed2c94dce" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" dependencies = [ "bitflags 2.13.0", "block2 0.6.2", "objc2 0.6.4", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", ] [[package]] @@ -5534,7 +5608,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -5988,7 +6062,7 @@ dependencies = [ "aes-gcm", "aes-kw", "argon2", - "base64 0.22.1", + "base64 0.21.7", "bitfield", "block-padding", "blowfish", @@ -7424,7 +7498,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7437,7 +7511,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7517,7 +7591,7 @@ dependencies = [ "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -7771,6 +7845,25 @@ dependencies = [ "smallvec", ] +[[package]] +name = "selectors" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d" +dependencies = [ + "bitflags 2.13.0", + "cssparser 0.37.0", + "derive_more 2.0.1", + "log", + "new_debug_unreachable", + "phf 0.13.1", + "phf_codegen 0.13.1", + "precomputed-hash", + "rustc-hash", + "servo_arc 0.4.3", + "smallvec", +] + [[package]] name = "semver" version = "1.0.28" @@ -8782,7 +8875,7 @@ dependencies = [ "ndk-sys", "objc2 0.6.4", "objc2-app-kit", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", "objc2-ui-kit", "once_cell", "parking_lot", @@ -8857,7 +8950,7 @@ dependencies = [ "muda", "objc2 0.6.4", "objc2-app-kit", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", "objc2-ui-kit", "objc2-web-kit", "percent-encoding", @@ -9175,7 +9268,7 @@ dependencies = [ "fern", "log", "objc2 0.6.4", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", "serde", "serde_json", "serde_repr", @@ -9279,7 +9372,7 @@ dependencies = [ "brotli", "cargo_metadata", "ctor", - "dom_query", + "dom_query 0.27.0", "dunce", "getrandom 0.3.4", "glob", @@ -9348,7 +9441,7 @@ dependencies = [ "getrandom 0.2.15", "once_cell", "rustix 0.38.43", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -9379,7 +9472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix 1.0.7", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -9820,12 +9913,12 @@ dependencies = [ "objc2-app-kit", "objc2-core-foundation", "objc2-core-graphics", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", "once_cell", "png 0.18.1", "serde", "thiserror 2.0.12", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -10616,7 +10709,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -10634,7 +10727,7 @@ dependencies = [ "objc2 0.6.4", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", "raw-window-handle", "windows-sys 0.59.0", "windows-version", @@ -11208,16 +11301,15 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wry" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3013fd6116aac351dd2e18f349b28b2cfef3a5ff3253a9d0ce2d7193bb1b4429" +version = "0.55.1" +source = "git+https://github.com/tauri-apps/wry?rev=e7c84dc2ee460933a4f61bab3c9f1b2d3d33e337#e7c84dc2ee460933a4f61bab3c9f1b2d3d33e337" dependencies = [ "base64 0.22.1", "block2 0.6.2", "cookie", "crossbeam-channel", "dirs 6.0.0", - "dom_query", + "dom_query 0.28.0", "dpi", "dunce", "gdkx11", @@ -11230,7 +11322,7 @@ dependencies = [ "objc2 0.6.4", "objc2-app-kit", "objc2-core-foundation", - "objc2-foundation 0.3.0", + "objc2-foundation 0.3.2", "objc2-ui-kit", "objc2-web-kit", "once_cell", diff --git a/crates/tauri-runtime-wry/Cargo.toml b/crates/tauri-runtime-wry/Cargo.toml index 7c576dea5ec2..bb379e3fcc4a 100644 --- a/crates/tauri-runtime-wry/Cargo.toml +++ b/crates/tauri-runtime-wry/Cargo.toml @@ -13,7 +13,7 @@ edition.workspace = true rust-version.workspace = true [dependencies] -wry = { version = "0.55.0", default-features = false, features = [ +wry = { git = "https://github.com/tauri-apps/wry", rev = "e7c84dc2ee460933a4f61bab3c9f1b2d3d33e337", default-features = false, features = [ "protocol", "os-webview", "linux-body", diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index c48b742af2e7..7174082b1e65 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -4872,6 +4872,48 @@ You may have it installed on another user account, but it is not available for t webview_builder.with_document_title_changed_handler(document_title_changed_handler) } + if let Some(permission_request_handler) = pending.permission_request_handler { + webview_builder = webview_builder.with_permission_handler(move |kind| { + let kind = match kind { + wry::PermissionKind::Microphone => tauri_runtime::webview::PermissionKind::Microphone, + wry::PermissionKind::Camera => tauri_runtime::webview::PermissionKind::Camera, + wry::PermissionKind::Geolocation => tauri_runtime::webview::PermissionKind::Geolocation, + wry::PermissionKind::Notifications => tauri_runtime::webview::PermissionKind::Notifications, + wry::PermissionKind::ClipboardRead => tauri_runtime::webview::PermissionKind::ClipboardRead, + wry::PermissionKind::DisplayCapture => { + tauri_runtime::webview::PermissionKind::DisplayCapture + } + wry::PermissionKind::Midi => tauri_runtime::webview::PermissionKind::Midi, + wry::PermissionKind::Sensors => tauri_runtime::webview::PermissionKind::Sensors, + wry::PermissionKind::MediaKeySystemAccess => { + tauri_runtime::webview::PermissionKind::MediaKeySystemAccess + } + wry::PermissionKind::LocalFonts => tauri_runtime::webview::PermissionKind::LocalFonts, + wry::PermissionKind::WindowManagement => { + tauri_runtime::webview::PermissionKind::WindowManagement + } + wry::PermissionKind::PointerLock => tauri_runtime::webview::PermissionKind::PointerLock, + wry::PermissionKind::AutomaticDownloads => { + tauri_runtime::webview::PermissionKind::AutomaticDownloads + } + wry::PermissionKind::FileSystemAccess => { + tauri_runtime::webview::PermissionKind::FileSystemAccess + } + wry::PermissionKind::Autoplay => tauri_runtime::webview::PermissionKind::Autoplay, + wry::PermissionKind::Other => tauri_runtime::webview::PermissionKind::Other, + _ => tauri_runtime::webview::PermissionKind::Other, + }; + + let response = permission_request_handler(kind); + + match response { + tauri_runtime::webview::PermissionResponse::Allow => wry::PermissionResponse::Allow, + tauri_runtime::webview::PermissionResponse::Deny => wry::PermissionResponse::Deny, + tauri_runtime::webview::PermissionResponse::Default => wry::PermissionResponse::Default, + } + }); + } + let webview_bounds = if let Some(bounds) = webview_attributes.bounds { let bounds: RectWrapper = bounds.into(); let bounds = bounds.0; diff --git a/crates/tauri-runtime/src/webview.rs b/crates/tauri-runtime/src/webview.rs index d7fb9c80f311..f9116f4b477f 100644 --- a/crates/tauri-runtime/src/webview.rs +++ b/crates/tauri-runtime/src/webview.rs @@ -41,6 +41,8 @@ type DocumentTitleChangedHandler = dyn Fn(String) + Send + 'static; type DownloadHandler = dyn Fn(DownloadEvent) -> bool + Send + Sync; +type PermissionRequestHandler = dyn Fn(PermissionKind) -> PermissionResponse + Send + Sync; + #[cfg(any(target_os = "macos", target_os = "ios"))] type OnWebContentProcessTerminateHandler = dyn Fn() + Send; @@ -72,6 +74,97 @@ pub enum DownloadEvent<'a> { }, } +/// Permission request response. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum PermissionResponse { + /// Permission allowed. + Allow, + /// Permission denied. + Deny, + /// Use the platform or browser default behavior. + #[default] + Default, +} + +impl std::fmt::Display for PermissionResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Allow => write!(f, "allow"), + Self::Deny => write!(f, "deny"), + Self::Default => write!(f, "default"), + } + } +} + +/// Permission request kind. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum PermissionKind { + /// Microphone access permission. + Microphone, + /// Camera access permission. + Camera, + /// Geolocation access permission. + Geolocation, + /// Notifications permission. + Notifications, + /// Clipboard read permission. + ClipboardRead, + /// Display capture permission (for getDisplayMedia). + DisplayCapture, + /// Midi access permission. + Midi, + /// Sensors (accelerometer, gyroscope, etc.) access permission. + Sensors, + /// Media key system access permission. + MediaKeySystemAccess, + /// Local fonts access permission. + LocalFonts, + /// Window management permission. + WindowManagement, + /// Pointer lock permission. + PointerLock, + /// Automatic downloads permission (multiple downloads without user interaction). + AutomaticDownloads, + /// File system access permission (read/write via File System Access API). + /// + /// ## Platform-specific + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_FILE_READ_WRITE`. + /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. + FileSystemAccess, + /// Media autoplay permission. + /// + /// ## Platform-specific + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_AUTOPLAY`. + /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. + Autoplay, + /// Other unrecognized permission type. + Other, +} + +impl std::fmt::Display for PermissionKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Microphone => write!(f, "microphone"), + Self::Camera => write!(f, "camera"), + Self::Geolocation => write!(f, "geolocation"), + Self::Notifications => write!(f, "notifications"), + Self::ClipboardRead => write!(f, "clipboard-read"), + Self::DisplayCapture => write!(f, "display-capture"), + Self::Midi => write!(f, "midi"), + Self::Sensors => write!(f, "sensors"), + Self::MediaKeySystemAccess => write!(f, "media-key-system-access"), + Self::LocalFonts => write!(f, "local-fonts"), + Self::WindowManagement => write!(f, "window-management"), + Self::PointerLock => write!(f, "pointer-lock"), + Self::AutomaticDownloads => write!(f, "automatic-downloads"), + Self::FileSystemAccess => write!(f, "file-system-access"), + Self::Autoplay => write!(f, "autoplay"), + Self::Other => write!(f, "other"), + } + } +} + #[cfg(target_os = "android")] pub struct CreationContext<'a, 'b> { pub env: &'a mut jni::JNIEnv<'b>, @@ -229,6 +322,8 @@ pub struct PendingWebview> { pub download_handler: Option>, + pub permission_request_handler: Option>, + #[cfg(any(target_os = "macos", target_os = "ios"))] pub on_web_content_process_terminate_handler: Option>, } @@ -257,6 +352,7 @@ impl> PendingWebview { web_resource_request_handler: None, on_page_load_handler: None, download_handler: None, + permission_request_handler: None, #[cfg(any(target_os = "macos", target_os = "ios"))] on_web_content_process_terminate_handler: None, }) diff --git a/crates/tauri/src/app.rs b/crates/tauri/src/app.rs index 27f2131dba59..5d77361c400d 100644 --- a/crates/tauri/src/app.rs +++ b/crates/tauri/src/app.rs @@ -1516,6 +1516,9 @@ pub struct Builder { /// Page load hook. on_page_load: Option>>, + /// Permission request hook. + on_permission_request: Option>>, + /// Web content process termination hook. #[cfg(any(target_os = "macos", target_os = "ios"))] on_web_content_process_terminate: Option>>, @@ -1606,6 +1609,7 @@ impl Builder { .into_string(), channel_interceptor: None, on_page_load: None, + on_permission_request: None, #[cfg(any(target_os = "macos", target_os = "ios"))] on_web_content_process_terminate: None, plugins: PluginStore::default(), @@ -1788,6 +1792,24 @@ tauri::Builder::default() self } + /// Defines a closure to be executed when a permission is requested. + /// + /// The handler receives the [`crate::webview::PermissionKind`] and should return + /// the desired [`crate::webview::PermissionResponse`]. + #[must_use] + pub fn on_permission_request(mut self, on_permission_request: F) -> Self + where + F: Fn(Webview, crate::webview::PermissionKind) -> crate::webview::PermissionResponse + + Send + + Sync + + 'static, + { + self + .on_permission_request + .replace(Arc::new(on_permission_request)); + self + } + /// Defines the web content process termination hook. /// /// ## Platform-specific @@ -2253,6 +2275,7 @@ tauri::Builder::default() self.plugins, self.invoke_handler, self.on_page_load, + self.on_permission_request, #[cfg(any(target_os = "macos", target_os = "ios"))] self.on_web_content_process_terminate, self.uri_scheme_protocols, diff --git a/crates/tauri/src/ipc/protocol.rs b/crates/tauri/src/ipc/protocol.rs index 455b1d01a42c..71fa95f15836 100644 --- a/crates/tauri/src/ipc/protocol.rs +++ b/crates/tauri/src/ipc/protocol.rs @@ -571,6 +571,7 @@ mod tests { #[cfg(any(target_os = "macos", target_os = "ios"))] None, Default::default(), + Default::default(), StateManager::new(), Default::default(), #[cfg(all(desktop, feature = "tray-icon"))] @@ -689,6 +690,7 @@ mod tests { #[cfg(any(target_os = "macos", target_os = "ios"))] None, Default::default(), + Default::default(), StateManager::new(), Default::default(), #[cfg(all(desktop, feature = "tray-icon"))] diff --git a/crates/tauri/src/manager/mod.rs b/crates/tauri/src/manager/mod.rs index 12b1df2e4e8a..8f2994b47087 100644 --- a/crates/tauri/src/manager/mod.rs +++ b/crates/tauri/src/manager/mod.rs @@ -254,6 +254,7 @@ impl AppManager { plugins: PluginStore, invoke_handler: Box>, on_page_load: Option>>, + on_permission_request: Option>>, #[cfg(any(target_os = "macos", target_os = "ios"))] on_web_content_process_terminate: Option< Arc>, >, @@ -290,6 +291,7 @@ impl AppManager { webviews: Mutex::default(), invoke_handler, on_page_load, + on_permission_request, #[cfg(any(target_os = "macos", target_os = "ios"))] on_web_content_process_terminate, uri_scheme_protocols: Mutex::new(uri_scheme_protocols), @@ -767,6 +769,7 @@ mod test { #[cfg(any(target_os = "macos", target_os = "ios"))] None, Default::default(), + Default::default(), StateManager::new(), Default::default(), #[cfg(all(desktop, feature = "tray-icon"))] diff --git a/crates/tauri/src/manager/webview.rs b/crates/tauri/src/manager/webview.rs index 8c99afb9ea47..70e0e47fd4d7 100644 --- a/crates/tauri/src/manager/webview.rs +++ b/crates/tauri/src/manager/webview.rs @@ -73,6 +73,8 @@ pub struct WebviewManager { pub invoke_handler: Box>, /// The page load hook, invoked when the webview performs a navigation. pub on_page_load: Option>>, + /// The permission request hook, invoked when the webview requests a permission. + pub on_permission_request: Option>>, /// The web content process termination hook. #[cfg(any(target_os = "macos", target_os = "ios"))] pub on_web_content_process_terminate: Option>>, @@ -310,6 +312,37 @@ impl WebviewManager { } })); + let label_ = pending.label.clone(); + let app_manager_ = manager.manager_owned(); + let permission_request_handler = pending.permission_request_handler.take(); + pending + .permission_request_handler + .replace(Box::new(move |kind| { + let mut response = crate::webview::PermissionResponse::Default; + + if let Some(handler) = &permission_request_handler { + response = handler(kind); + } + + if let Some(w) = app_manager_.get_webview(&label_) { + if response == crate::webview::PermissionResponse::Default { + if let Some(on_permission_request) = &app_manager_.webview.on_permission_request { + response = on_permission_request(w.clone(), kind); + } + } + + if response == crate::webview::PermissionResponse::Default { + response = app_manager_ + .plugins + .lock() + .unwrap() + .on_permission_request(&w, kind); + } + } + + response + })); + #[cfg(any(target_os = "macos", target_os = "ios"))] if pending.on_web_content_process_terminate_handler.is_none() { let app_manager_ = manager.manager_owned(); diff --git a/crates/tauri/src/plugin.rs b/crates/tauri/src/plugin.rs index 3a22de7bc700..113b5779f90c 100644 --- a/crates/tauri/src/plugin.rs +++ b/crates/tauri/src/plugin.rs @@ -100,6 +100,16 @@ pub trait Plugin: Send { #[allow(unused_variables)] fn on_page_load(&mut self, webview: &Webview, payload: &PageLoadPayload<'_>) {} + /// Callback invoked when the webview requests a permission. + #[allow(unused_variables)] + fn on_permission_request( + &mut self, + webview: &Webview, + kind: crate::webview::PermissionKind, + ) -> crate::webview::PermissionResponse { + crate::webview::PermissionResponse::Default + } + /// Callback invoked when the event loop receives a new event. #[allow(unused_variables)] fn on_event(&mut self, app: &AppHandle, event: &RunEvent) {} @@ -118,6 +128,8 @@ type OnWebviewReady = dyn FnMut(Webview) + Send; type OnEvent = dyn FnMut(&AppHandle, &RunEvent) + Send; type OnNavigation = dyn Fn(&Webview, &Url) -> bool + Send; type OnPageLoad = dyn FnMut(&Webview, &PageLoadPayload<'_>) + Send; +type OnPermissionRequest = dyn FnMut(&Webview, crate::webview::PermissionKind) -> crate::webview::PermissionResponse + + Send; type OnDrop = dyn FnOnce(AppHandle) + Send; /// A handle to a plugin. @@ -268,6 +280,7 @@ pub struct Builder { js_init_script: Option, on_navigation: Box>, on_page_load: Box>, + on_permission_request: Box>, on_window_ready: Box>, on_webview_ready: Box>, on_event: Box>, @@ -285,6 +298,7 @@ impl Builder { invoke_handler: Box::new(|_| false), on_navigation: Box::new(|_, _| true), on_page_load: Box::new(|_, _| ()), + on_permission_request: Box::new(|_, _| crate::webview::PermissionResponse::Default), on_window_ready: Box::new(|_| ()), on_webview_ready: Box::new(|_| ()), on_event: Box::new(|_, _| ()), @@ -487,6 +501,33 @@ impl Builder { self } + /// Callback invoked when the webview requests a permission. + /// + /// # Examples + /// + /// ```rust + /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime}; + /// + /// fn init() -> TauriPlugin { + /// Builder::new("example") + /// .on_permission_request(|webview, kind| { + /// println!("Permission {} requested in webview {}", kind, webview.label()); + /// tauri::webview::PermissionResponse::Default + /// }) + /// .build() + /// } + /// ``` + #[must_use] + pub fn on_permission_request(mut self, on_permission_request: F) -> Self + where + F: FnMut(&Webview, crate::webview::PermissionKind) -> crate::webview::PermissionResponse + + Send + + 'static, + { + self.on_permission_request = Box::new(on_permission_request); + self + } + /// Callback invoked when the window is created. /// /// # Examples @@ -737,6 +778,7 @@ impl Builder { js_init_script: self.js_init_script, on_navigation: self.on_navigation, on_page_load: self.on_page_load, + on_permission_request: self.on_permission_request, on_window_ready: self.on_window_ready, on_webview_ready: self.on_webview_ready, on_event: self.on_event, @@ -764,6 +806,7 @@ pub struct TauriPlugin { js_init_script: Option, on_navigation: Box>, on_page_load: Box>, + on_permission_request: Box>, on_window_ready: Box>, on_webview_ready: Box>, on_event: Box>, @@ -843,6 +886,14 @@ impl Plugin for TauriPlugin { (self.on_page_load)(webview, payload) } + fn on_permission_request( + &mut self, + webview: &Webview, + kind: crate::webview::PermissionKind, + ) -> crate::webview::PermissionResponse { + (self.on_permission_request)(webview, kind) + } + fn on_event(&mut self, app: &AppHandle, event: &RunEvent) { (self.on_event)(app, event) } @@ -971,6 +1022,25 @@ impl PluginStore { }) } + /// Runs the on_permission_request hook for all plugins in the store. + pub(crate) fn on_permission_request( + &mut self, + webview: &Webview, + kind: crate::webview::PermissionKind, + ) -> crate::webview::PermissionResponse { + for plugin in self.store.iter_mut() { + #[cfg(feature = "tracing")] + let _span = + tracing::trace_span!("plugin::hooks::on_permission_request", name = plugin.name()) + .entered(); + let res = plugin.on_permission_request(webview, kind); + if res != crate::webview::PermissionResponse::Default { + return res; + } + } + crate::webview::PermissionResponse::Default + } + /// Runs the on_event hook for all plugins in the store. pub(crate) fn on_event(&mut self, app: &AppHandle, event: &RunEvent) { self diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index b89d3c95d0e8..6ff2230a1142 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -18,7 +18,9 @@ pub use cookie; use http::HeaderMap; use serde::Serialize; use tauri_macros::default_runtime; -pub use tauri_runtime::webview::{NewWindowFeatures, PageLoadEvent, ScrollBarStyle}; +pub use tauri_runtime::webview::{ + NewWindowFeatures, PageLoadEvent, PermissionKind, PermissionResponse, ScrollBarStyle, +}; // Remove this re-export in v3 pub use tauri_runtime::Cookie; #[cfg(desktop)] @@ -64,6 +66,8 @@ pub(crate) type UriSchemeProtocolHandler = pub(crate) type OnPageLoad = dyn Fn(Webview, PageLoadPayload<'_>) + Send + Sync + 'static; pub(crate) type OnDocumentTitleChanged = dyn Fn(Webview, String) + Send + 'static; pub(crate) type DownloadHandler = dyn Fn(Webview, DownloadEvent<'_>) -> bool + Send + Sync; +pub(crate) type PermissionRequestHandler = + dyn Fn(Webview, PermissionKind) -> PermissionResponse + Send + Sync + 'static; #[derive(Clone, Serialize)] pub(crate) struct CreatedEvent { @@ -277,6 +281,7 @@ unstable_struct!( pub(crate) on_page_load_handler: Option>>, pub(crate) document_title_changed_handler: Option>>, pub(crate) download_handler: Option>>, + pub(crate) permission_request_handler: Option>>, } ); @@ -355,6 +360,7 @@ async fn create_window(app: tauri::AppHandle) { on_page_load_handler: None, document_title_changed_handler: None, download_handler: None, + permission_request_handler: None, } } @@ -434,6 +440,7 @@ async fn create_window(app: tauri::AppHandle) { on_page_load_handler: None, document_title_changed_handler: None, download_handler: None, + permission_request_handler: None, } } @@ -693,6 +700,52 @@ tauri::Builder::default() self } + /// Defines a closure to be executed when a permission is requested. + /// + /// The handler receives the [`PermissionKind`] and should return + /// the desired [`PermissionResponse`]. + /// + /// > [!NOTE] + /// > This handler only triggers for new permission requests. If the user has already + /// > allowed or denied a permission persistently within the webview, the browser + /// > will use the saved preference instead of calling this handler. + /// + /// ## Platform-specific: + /// + /// - **Windows**: Fully supported via WebView2's PermissionRequested event. + /// - **macOS / iOS**: Fully supported via WKUIDelegate's requestMediaCapturePermission. + /// - **Linux**: Fully supported via WebKitGTK's permission-request signal. + /// - **Android**: Experimental support implemented via JNI. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::webview::{WebviewBuilder, PermissionKind, PermissionResponse}; + /// tauri::Builder::default() + /// .setup(|app| { + /// let window = tauri::window::WindowBuilder::new(app, "label").build()?; + /// let webview_builder = WebviewBuilder::new("core", tauri::WebviewUrl::App("index.html".into())) + /// .on_permission_request(|webview, kind| { + /// match kind { + /// PermissionKind::Geolocation => PermissionResponse::Allow, + /// PermissionKind::Notifications => PermissionResponse::Allow, + /// _ => PermissionResponse::Default, + /// } + /// }); + /// let webview = window.add_child(webview_builder, tauri::LogicalPosition::new(0, 0), window.inner_size().unwrap())?; + /// Ok(()) + /// }); + /// ``` + pub fn on_permission_request< + F: Fn(Webview, PermissionKind) -> PermissionResponse + Send + Sync + 'static, + >( + mut self, + f: F, + ) -> Self { + self.permission_request_handler.replace(Box::new(f)); + self + } + pub(crate) fn into_pending_webview>( mut self, manager: &M, @@ -771,6 +824,18 @@ tauri::Builder::default() } })); + let label_ = pending.label.clone(); + let manager_ = manager.manager_owned(); + if let Some(handler) = self.permission_request_handler { + pending.permission_request_handler = Some(Box::new(move |kind| { + if let Some(w) = manager_.get_webview(&label_) { + handler(w, kind) + } else { + PermissionResponse::Default + } + })); + } + manager .manager() .webview diff --git a/crates/tauri/src/webview/webview_window.rs b/crates/tauri/src/webview/webview_window.rs index 33a393ded798..935d37211291 100644 --- a/crates/tauri/src/webview/webview_window.rs +++ b/crates/tauri/src/webview/webview_window.rs @@ -434,6 +434,20 @@ tauri::Builder::default() self } + /// Defines a closure to be executed when a permission is requested. + pub fn on_permission_request< + F: Fn(Webview, crate::webview::PermissionKind) -> crate::webview::PermissionResponse + + Send + + Sync + + 'static, + >( + mut self, + f: F, + ) -> Self { + self.webview_builder = self.webview_builder.on_permission_request(f); + self + } + /// Creates a new window. pub fn build(self) -> crate::Result> { let (window, webview) = self.window_builder.with_webview(self.webview_builder)?; From 2bad4b917f0b17168e259813f84340be23bed20b Mon Sep 17 00:00:00 2001 From: F0RLE Date: Tue, 21 Jul 2026 12:33:32 +0300 Subject: [PATCH 2/7] fix: preserve default permission behavior --- crates/tauri-runtime/src/lib.rs | 1 + crates/tauri-runtime/src/webview.rs | 92 +--------- .../tauri-runtime/src/webview_permissions.rs | 161 ++++++++++++++++++ crates/tauri/src/manager/webview.rs | 41 ++--- crates/tauri/src/plugin.rs | 70 -------- crates/tauri/src/webview/mod.rs | 16 +- 6 files changed, 195 insertions(+), 186 deletions(-) create mode 100644 crates/tauri-runtime/src/webview_permissions.rs diff --git a/crates/tauri-runtime/src/lib.rs b/crates/tauri-runtime/src/lib.rs index eac06fbe95ef..af811c20e7f0 100644 --- a/crates/tauri-runtime/src/lib.rs +++ b/crates/tauri-runtime/src/lib.rs @@ -26,6 +26,7 @@ pub mod dpi; /// Types useful for interacting with a user's monitors. pub mod monitor; pub mod webview; +mod webview_permissions; pub mod window; use dpi::{PhysicalPosition, PhysicalSize, Position, Rect, Size}; diff --git a/crates/tauri-runtime/src/webview.rs b/crates/tauri-runtime/src/webview.rs index f9116f4b477f..f74701bc3605 100644 --- a/crates/tauri-runtime/src/webview.rs +++ b/crates/tauri-runtime/src/webview.rs @@ -4,6 +4,7 @@ //! A layer between raw [`Runtime`] webviews and Tauri. //! +pub use crate::webview_permissions::{PermissionKind, PermissionResponse}; #[cfg(not(any(target_os = "android", target_os = "ios")))] use crate::window::WindowId; use crate::{window::is_label_valid, Rect, Runtime, UserEvent}; @@ -74,97 +75,6 @@ pub enum DownloadEvent<'a> { }, } -/// Permission request response. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] -pub enum PermissionResponse { - /// Permission allowed. - Allow, - /// Permission denied. - Deny, - /// Use the platform or browser default behavior. - #[default] - Default, -} - -impl std::fmt::Display for PermissionResponse { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Allow => write!(f, "allow"), - Self::Deny => write!(f, "deny"), - Self::Default => write!(f, "default"), - } - } -} - -/// Permission request kind. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub enum PermissionKind { - /// Microphone access permission. - Microphone, - /// Camera access permission. - Camera, - /// Geolocation access permission. - Geolocation, - /// Notifications permission. - Notifications, - /// Clipboard read permission. - ClipboardRead, - /// Display capture permission (for getDisplayMedia). - DisplayCapture, - /// Midi access permission. - Midi, - /// Sensors (accelerometer, gyroscope, etc.) access permission. - Sensors, - /// Media key system access permission. - MediaKeySystemAccess, - /// Local fonts access permission. - LocalFonts, - /// Window management permission. - WindowManagement, - /// Pointer lock permission. - PointerLock, - /// Automatic downloads permission (multiple downloads without user interaction). - AutomaticDownloads, - /// File system access permission (read/write via File System Access API). - /// - /// ## Platform-specific - /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_FILE_READ_WRITE`. - /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. - FileSystemAccess, - /// Media autoplay permission. - /// - /// ## Platform-specific - /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_AUTOPLAY`. - /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. - Autoplay, - /// Other unrecognized permission type. - Other, -} - -impl std::fmt::Display for PermissionKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Microphone => write!(f, "microphone"), - Self::Camera => write!(f, "camera"), - Self::Geolocation => write!(f, "geolocation"), - Self::Notifications => write!(f, "notifications"), - Self::ClipboardRead => write!(f, "clipboard-read"), - Self::DisplayCapture => write!(f, "display-capture"), - Self::Midi => write!(f, "midi"), - Self::Sensors => write!(f, "sensors"), - Self::MediaKeySystemAccess => write!(f, "media-key-system-access"), - Self::LocalFonts => write!(f, "local-fonts"), - Self::WindowManagement => write!(f, "window-management"), - Self::PointerLock => write!(f, "pointer-lock"), - Self::AutomaticDownloads => write!(f, "automatic-downloads"), - Self::FileSystemAccess => write!(f, "file-system-access"), - Self::Autoplay => write!(f, "autoplay"), - Self::Other => write!(f, "other"), - } - } -} - #[cfg(target_os = "android")] pub struct CreationContext<'a, 'b> { pub env: &'a mut jni::JNIEnv<'b>, diff --git a/crates/tauri-runtime/src/webview_permissions.rs b/crates/tauri-runtime/src/webview_permissions.rs new file mode 100644 index 000000000000..7221c7ecbe19 --- /dev/null +++ b/crates/tauri-runtime/src/webview_permissions.rs @@ -0,0 +1,161 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +/// Permission types that can be requested by the webview. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub enum PermissionKind { + /// Microphone access permission. + Microphone, + /// Camera access permission. + Camera, + /// Geolocation access permission. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_GEOLOCATION`. + /// - **Linux**: Supported via `GeolocationPermissionRequest`. + /// - **Android**: Supported via `WebChromeClient.onGeolocationPermissionsShowPrompt`. + /// - **macOS / iOS**: Not yet supported by platform backends. + Geolocation, + /// Notifications permission. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS`. + /// - **Linux**: Supported via `NotificationPermissionRequest`. + /// - **macOS / Android / iOS**: Not yet supported by platform backends. + Notifications, + /// Clipboard read permission. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ`. + /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. + ClipboardRead, + /// Display capture permission (for getDisplayMedia). + DisplayCapture, + /// Midi access permission. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_MIDI_SYSTEM_EXCLUSIVE_MESSAGES`. + /// - **Android**: Supported via `android.webkit.resource.MIDI_SYSEX`. + /// - **macOS / Linux / iOS**: Not yet supported by platform backends. + Midi, + /// Sensors (accelerometer, gyroscope, etc.) access permission. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_OTHER_SENSORS`. + /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. + Sensors, + /// Media key system access permission. + /// + /// ## Platform-specific + /// + /// - **Android**: Supported via `android.webkit.resource.PROTECTED_MEDIA_ID`. + /// - **Windows / macOS / Linux / iOS**: Not yet supported by platform backends. + MediaKeySystemAccess, + /// Local fonts access permission. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_LOCAL_FONTS`. + /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. + LocalFonts, + /// Window management permission. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_WINDOW_MANAGEMENT`. + /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. + WindowManagement, + /// Pointer lock permission. + /// + /// ## Platform-specific + /// + /// - **Linux**: Supported via `PointerLockPermissionRequest`. + /// - **Windows / macOS / Android / iOS**: Not yet supported by platform backends. + PointerLock, + /// Automatic downloads permission (multiple downloads without user interaction). + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_MULTIPLE_AUTOMATIC_DOWNLOADS`. + /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. + AutomaticDownloads, + /// File system access permission (read/write via File System Access API). + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_FILE_READ_WRITE`. + /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. + FileSystemAccess, + /// Media autoplay permission. + /// + /// ## Platform-specific + /// + /// - **Windows**: Supported via `COREWEBVIEW2_PERMISSION_KIND_AUTOPLAY`. + /// - **macOS / Linux / Android / iOS**: Not yet supported by platform backends. + Autoplay, + /// Other unrecognized permission type. + Other, +} + +impl std::fmt::Display for PermissionKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Microphone => write!(f, "microphone"), + Self::Camera => write!(f, "camera"), + Self::Geolocation => write!(f, "geolocation"), + Self::Notifications => write!(f, "notifications"), + Self::ClipboardRead => write!(f, "clipboard-read"), + Self::DisplayCapture => write!(f, "display-capture"), + Self::Midi => write!(f, "midi"), + Self::Sensors => write!(f, "sensors"), + Self::MediaKeySystemAccess => write!(f, "media-key-system-access"), + Self::LocalFonts => write!(f, "local-fonts"), + Self::WindowManagement => write!(f, "window-management"), + Self::PointerLock => write!(f, "pointer-lock"), + Self::AutomaticDownloads => write!(f, "automatic-downloads"), + Self::FileSystemAccess => write!(f, "file-system-access"), + Self::Autoplay => write!(f, "autoplay"), + Self::Other => write!(f, "other"), + } + } +} + +/// Response for permission requests. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum PermissionResponse { + /// Grant the permission. + /// + /// ## Platform-specific + /// + /// - **Android**: Not supported for runtime permissions; the normal Android + /// permission flow is used instead. + Allow, + /// Deny the permission. + Deny, + /// Use the platform or browser default behavior. + /// + /// ## Platform-specific + /// + /// - **Windows / macOS / Android**: The default behavior is to continue the + /// platform or browser permission flow. + /// - **Linux**: The default behavior is [`Self::Deny`] + #[default] + Default, +} + +impl std::fmt::Display for PermissionResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Allow => write!(f, "allow"), + Self::Deny => write!(f, "deny"), + Self::Default => write!(f, "default"), + } + } +} diff --git a/crates/tauri/src/manager/webview.rs b/crates/tauri/src/manager/webview.rs index 70e0e47fd4d7..9a871990740e 100644 --- a/crates/tauri/src/manager/webview.rs +++ b/crates/tauri/src/manager/webview.rs @@ -312,36 +312,29 @@ impl WebviewManager { } })); - let label_ = pending.label.clone(); - let app_manager_ = manager.manager_owned(); let permission_request_handler = pending.permission_request_handler.take(); - pending - .permission_request_handler - .replace(Box::new(move |kind| { - let mut response = crate::webview::PermissionResponse::Default; - - if let Some(handler) = &permission_request_handler { - response = handler(kind); - } - - if let Some(w) = app_manager_.get_webview(&label_) { - if response == crate::webview::PermissionResponse::Default { - if let Some(on_permission_request) = &app_manager_.webview.on_permission_request { - response = on_permission_request(w.clone(), kind); + if permission_request_handler.is_some() || self.on_permission_request.is_some() { + let label_ = pending.label.clone(); + let app_manager_ = manager.manager_owned(); + pending + .permission_request_handler + .replace(Box::new(move |kind| { + if let Some(handler) = &permission_request_handler { + let response = handler(kind); + if response != crate::webview::PermissionResponse::Default { + return response; } } - if response == crate::webview::PermissionResponse::Default { - response = app_manager_ - .plugins - .lock() - .unwrap() - .on_permission_request(&w, kind); + if let Some(w) = app_manager_.get_webview(&label_) { + if let Some(on_permission_request) = &app_manager_.webview.on_permission_request { + return on_permission_request(w, kind); + } } - } - response - })); + crate::webview::PermissionResponse::Default + })); + } #[cfg(any(target_os = "macos", target_os = "ios"))] if pending.on_web_content_process_terminate_handler.is_none() { diff --git a/crates/tauri/src/plugin.rs b/crates/tauri/src/plugin.rs index 113b5779f90c..3a22de7bc700 100644 --- a/crates/tauri/src/plugin.rs +++ b/crates/tauri/src/plugin.rs @@ -100,16 +100,6 @@ pub trait Plugin: Send { #[allow(unused_variables)] fn on_page_load(&mut self, webview: &Webview, payload: &PageLoadPayload<'_>) {} - /// Callback invoked when the webview requests a permission. - #[allow(unused_variables)] - fn on_permission_request( - &mut self, - webview: &Webview, - kind: crate::webview::PermissionKind, - ) -> crate::webview::PermissionResponse { - crate::webview::PermissionResponse::Default - } - /// Callback invoked when the event loop receives a new event. #[allow(unused_variables)] fn on_event(&mut self, app: &AppHandle, event: &RunEvent) {} @@ -128,8 +118,6 @@ type OnWebviewReady = dyn FnMut(Webview) + Send; type OnEvent = dyn FnMut(&AppHandle, &RunEvent) + Send; type OnNavigation = dyn Fn(&Webview, &Url) -> bool + Send; type OnPageLoad = dyn FnMut(&Webview, &PageLoadPayload<'_>) + Send; -type OnPermissionRequest = dyn FnMut(&Webview, crate::webview::PermissionKind) -> crate::webview::PermissionResponse - + Send; type OnDrop = dyn FnOnce(AppHandle) + Send; /// A handle to a plugin. @@ -280,7 +268,6 @@ pub struct Builder { js_init_script: Option, on_navigation: Box>, on_page_load: Box>, - on_permission_request: Box>, on_window_ready: Box>, on_webview_ready: Box>, on_event: Box>, @@ -298,7 +285,6 @@ impl Builder { invoke_handler: Box::new(|_| false), on_navigation: Box::new(|_, _| true), on_page_load: Box::new(|_, _| ()), - on_permission_request: Box::new(|_, _| crate::webview::PermissionResponse::Default), on_window_ready: Box::new(|_| ()), on_webview_ready: Box::new(|_| ()), on_event: Box::new(|_, _| ()), @@ -501,33 +487,6 @@ impl Builder { self } - /// Callback invoked when the webview requests a permission. - /// - /// # Examples - /// - /// ```rust - /// use tauri::{plugin::{Builder, TauriPlugin}, Runtime}; - /// - /// fn init() -> TauriPlugin { - /// Builder::new("example") - /// .on_permission_request(|webview, kind| { - /// println!("Permission {} requested in webview {}", kind, webview.label()); - /// tauri::webview::PermissionResponse::Default - /// }) - /// .build() - /// } - /// ``` - #[must_use] - pub fn on_permission_request(mut self, on_permission_request: F) -> Self - where - F: FnMut(&Webview, crate::webview::PermissionKind) -> crate::webview::PermissionResponse - + Send - + 'static, - { - self.on_permission_request = Box::new(on_permission_request); - self - } - /// Callback invoked when the window is created. /// /// # Examples @@ -778,7 +737,6 @@ impl Builder { js_init_script: self.js_init_script, on_navigation: self.on_navigation, on_page_load: self.on_page_load, - on_permission_request: self.on_permission_request, on_window_ready: self.on_window_ready, on_webview_ready: self.on_webview_ready, on_event: self.on_event, @@ -806,7 +764,6 @@ pub struct TauriPlugin { js_init_script: Option, on_navigation: Box>, on_page_load: Box>, - on_permission_request: Box>, on_window_ready: Box>, on_webview_ready: Box>, on_event: Box>, @@ -886,14 +843,6 @@ impl Plugin for TauriPlugin { (self.on_page_load)(webview, payload) } - fn on_permission_request( - &mut self, - webview: &Webview, - kind: crate::webview::PermissionKind, - ) -> crate::webview::PermissionResponse { - (self.on_permission_request)(webview, kind) - } - fn on_event(&mut self, app: &AppHandle, event: &RunEvent) { (self.on_event)(app, event) } @@ -1022,25 +971,6 @@ impl PluginStore { }) } - /// Runs the on_permission_request hook for all plugins in the store. - pub(crate) fn on_permission_request( - &mut self, - webview: &Webview, - kind: crate::webview::PermissionKind, - ) -> crate::webview::PermissionResponse { - for plugin in self.store.iter_mut() { - #[cfg(feature = "tracing")] - let _span = - tracing::trace_span!("plugin::hooks::on_permission_request", name = plugin.name()) - .entered(); - let res = plugin.on_permission_request(webview, kind); - if res != crate::webview::PermissionResponse::Default { - return res; - } - } - crate::webview::PermissionResponse::Default - } - /// Runs the on_event hook for all plugins in the store. pub(crate) fn on_event(&mut self, app: &AppHandle, event: &RunEvent) { self diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index 6ff2230a1142..7b03b57e98cb 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -715,7 +715,9 @@ tauri::Builder::default() /// - **Windows**: Fully supported via WebView2's PermissionRequested event. /// - **macOS / iOS**: Fully supported via WKUIDelegate's requestMediaCapturePermission. /// - **Linux**: Fully supported via WebKitGTK's permission-request signal. - /// - **Android**: Experimental support implemented via JNI. + /// - **Android**: Supported via JNI bridge for geolocation, microphone, camera, + /// protected media, and MIDI requests. Android runtime permissions may still + /// trigger native OS prompts before access is granted. /// /// # Examples /// @@ -2421,6 +2423,18 @@ impl ResolvedScope { mod tests { use url::Url; + #[test] + fn no_permission_handler_preserves_runtime_default() { + use crate::test::{mock_builder, mock_context, noop_assets}; + + let app = mock_builder().build(mock_context(noop_assets())).unwrap(); + let pending = super::WebviewBuilder::new("test", crate::WebviewUrl::default()) + .into_pending_webview(&app, "test") + .unwrap(); + + assert!(pending.permission_request_handler.is_none()); + } + fn test_webview_window() -> crate::WebviewWindow { use crate::test::{mock_builder, mock_context, noop_assets}; From d7f2c09371bba31120440326274d7c5a6bd3d41e Mon Sep 17 00:00:00 2001 From: F0RLE Date: Thu, 23 Jul 2026 00:55:32 +0300 Subject: [PATCH 3/7] refactor: move webview permission mappings --- crates/tauri-runtime-wry/src/lib.rs | 39 ++----------------- .../src/webview_permissions.rs | 35 +++++++++++++++++ crates/tauri/src/app.rs | 26 +++++++++++++ crates/tauri/src/webview/webview_window.rs | 37 ++++++++++++++++++ 4 files changed, 101 insertions(+), 36 deletions(-) create mode 100644 crates/tauri-runtime-wry/src/webview_permissions.rs diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index ae8f036b2d57..12c3bccf884e 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -154,6 +154,7 @@ mod monitor; mod undecorated_resizing; mod util; mod webview; +mod webview_permissions; mod window; pub use webview::Webview; @@ -4872,43 +4873,9 @@ You may have it installed on another user account, but it is not available for t if let Some(permission_request_handler) = pending.permission_request_handler { webview_builder = webview_builder.with_permission_handler(move |kind| { - let kind = match kind { - wry::PermissionKind::Microphone => tauri_runtime::webview::PermissionKind::Microphone, - wry::PermissionKind::Camera => tauri_runtime::webview::PermissionKind::Camera, - wry::PermissionKind::Geolocation => tauri_runtime::webview::PermissionKind::Geolocation, - wry::PermissionKind::Notifications => tauri_runtime::webview::PermissionKind::Notifications, - wry::PermissionKind::ClipboardRead => tauri_runtime::webview::PermissionKind::ClipboardRead, - wry::PermissionKind::DisplayCapture => { - tauri_runtime::webview::PermissionKind::DisplayCapture - } - wry::PermissionKind::Midi => tauri_runtime::webview::PermissionKind::Midi, - wry::PermissionKind::Sensors => tauri_runtime::webview::PermissionKind::Sensors, - wry::PermissionKind::MediaKeySystemAccess => { - tauri_runtime::webview::PermissionKind::MediaKeySystemAccess - } - wry::PermissionKind::LocalFonts => tauri_runtime::webview::PermissionKind::LocalFonts, - wry::PermissionKind::WindowManagement => { - tauri_runtime::webview::PermissionKind::WindowManagement - } - wry::PermissionKind::PointerLock => tauri_runtime::webview::PermissionKind::PointerLock, - wry::PermissionKind::AutomaticDownloads => { - tauri_runtime::webview::PermissionKind::AutomaticDownloads - } - wry::PermissionKind::FileSystemAccess => { - tauri_runtime::webview::PermissionKind::FileSystemAccess - } - wry::PermissionKind::Autoplay => tauri_runtime::webview::PermissionKind::Autoplay, - wry::PermissionKind::Other => tauri_runtime::webview::PermissionKind::Other, - _ => tauri_runtime::webview::PermissionKind::Other, - }; - + let kind = webview_permissions::map_permission_kind(kind); let response = permission_request_handler(kind); - - match response { - tauri_runtime::webview::PermissionResponse::Allow => wry::PermissionResponse::Allow, - tauri_runtime::webview::PermissionResponse::Deny => wry::PermissionResponse::Deny, - tauri_runtime::webview::PermissionResponse::Default => wry::PermissionResponse::Default, - } + webview_permissions::map_permission_response(response) }); } diff --git a/crates/tauri-runtime-wry/src/webview_permissions.rs b/crates/tauri-runtime-wry/src/webview_permissions.rs new file mode 100644 index 000000000000..9543d8a74797 --- /dev/null +++ b/crates/tauri-runtime-wry/src/webview_permissions.rs @@ -0,0 +1,35 @@ +// Copyright 2019-2024 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tauri_runtime::webview::{PermissionKind, PermissionResponse}; + +pub(super) fn map_permission_kind(kind: wry::PermissionKind) -> PermissionKind { + match kind { + wry::PermissionKind::Microphone => PermissionKind::Microphone, + wry::PermissionKind::Camera => PermissionKind::Camera, + wry::PermissionKind::Geolocation => PermissionKind::Geolocation, + wry::PermissionKind::Notifications => PermissionKind::Notifications, + wry::PermissionKind::ClipboardRead => PermissionKind::ClipboardRead, + wry::PermissionKind::DisplayCapture => PermissionKind::DisplayCapture, + wry::PermissionKind::Midi => PermissionKind::Midi, + wry::PermissionKind::Sensors => PermissionKind::Sensors, + wry::PermissionKind::MediaKeySystemAccess => PermissionKind::MediaKeySystemAccess, + wry::PermissionKind::LocalFonts => PermissionKind::LocalFonts, + wry::PermissionKind::WindowManagement => PermissionKind::WindowManagement, + wry::PermissionKind::PointerLock => PermissionKind::PointerLock, + wry::PermissionKind::AutomaticDownloads => PermissionKind::AutomaticDownloads, + wry::PermissionKind::FileSystemAccess => PermissionKind::FileSystemAccess, + wry::PermissionKind::Autoplay => PermissionKind::Autoplay, + wry::PermissionKind::Other => PermissionKind::Other, + _ => PermissionKind::Other, + } +} + +pub(super) fn map_permission_response(response: PermissionResponse) -> wry::PermissionResponse { + match response { + PermissionResponse::Allow => wry::PermissionResponse::Allow, + PermissionResponse::Deny => wry::PermissionResponse::Deny, + PermissionResponse::Default => wry::PermissionResponse::Default, + } +} diff --git a/crates/tauri/src/app.rs b/crates/tauri/src/app.rs index 5d77361c400d..bc050eae5113 100644 --- a/crates/tauri/src/app.rs +++ b/crates/tauri/src/app.rs @@ -1796,6 +1796,32 @@ tauri::Builder::default() /// /// The handler receives the [`crate::webview::PermissionKind`] and should return /// the desired [`crate::webview::PermissionResponse`]. + /// + /// > [!NOTE] + /// > This handler only triggers for new permission requests. If the user has already + /// > allowed or denied a permission persistently within the webview, the browser + /// > will use the saved preference instead of calling this handler. + /// + /// ## Platform-specific: + /// + /// - **Windows**: Fully supported via WebView2's PermissionRequested event. + /// - **macOS / iOS**: Fully supported via WKUIDelegate's requestMediaCapturePermission. + /// - **Linux**: Fully supported via WebKitGTK's permission-request signal. + /// - **Android**: Supported via JNI bridge for geolocation, microphone, camera, + /// protected media, and MIDI requests. Android runtime permissions may still + /// trigger native OS prompts before access is granted. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::webview::{PermissionKind, PermissionResponse}; + /// tauri::Builder::default() + /// .on_permission_request(|_, kind| match kind { + /// PermissionKind::Geolocation => PermissionResponse::Allow, + /// PermissionKind::Notifications => PermissionResponse::Allow, + /// _ => PermissionResponse::Default, + /// }); + /// ``` #[must_use] pub fn on_permission_request(mut self, on_permission_request: F) -> Self where diff --git a/crates/tauri/src/webview/webview_window.rs b/crates/tauri/src/webview/webview_window.rs index 935d37211291..7832a6fd95f6 100644 --- a/crates/tauri/src/webview/webview_window.rs +++ b/crates/tauri/src/webview/webview_window.rs @@ -435,6 +435,43 @@ tauri::Builder::default() } /// Defines a closure to be executed when a permission is requested. + /// + /// The handler receives the [`crate::webview::PermissionKind`] and should return + /// the desired [`crate::webview::PermissionResponse`]. + /// + /// > [!NOTE] + /// > This handler only triggers for new permission requests. If the user has already + /// > allowed or denied a permission persistently within the webview, the browser + /// > will use the saved preference instead of calling this handler. + /// + /// ## Platform-specific: + /// + /// - **Windows**: Fully supported via WebView2's PermissionRequested event. + /// - **macOS / iOS**: Fully supported via WKUIDelegate's requestMediaCapturePermission. + /// - **Linux**: Fully supported via WebKitGTK's permission-request signal. + /// - **Android**: Supported via JNI bridge for geolocation, microphone, camera, + /// protected media, and MIDI requests. Android runtime permissions may still + /// trigger native OS prompts before access is granted. + /// + /// # Examples + /// + /// ```rust,no_run + /// use tauri::{ + /// webview::{PermissionKind, PermissionResponse, WebviewWindowBuilder}, + /// WebviewUrl, + /// }; + /// tauri::Builder::default() + /// .setup(|app| { + /// WebviewWindowBuilder::new(app, "core", WebviewUrl::App("index.html".into())) + /// .on_permission_request(|_, kind| match kind { + /// PermissionKind::Geolocation => PermissionResponse::Allow, + /// PermissionKind::Notifications => PermissionResponse::Allow, + /// _ => PermissionResponse::Default, + /// }) + /// .build()?; + /// Ok(()) + /// }); + /// ``` pub fn on_permission_request< F: Fn(Webview, crate::webview::PermissionKind) -> crate::webview::PermissionResponse + Send From d173e8cd7566e110cf04f8e72916e50874a32943 Mon Sep 17 00:00:00 2001 From: F0RLE Date: Thu, 23 Jul 2026 09:47:18 +0300 Subject: [PATCH 4/7] refactor: rename permission mapping helpers --- crates/tauri-runtime-wry/src/lib.rs | 4 ++-- crates/tauri-runtime-wry/src/webview_permissions.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index 12c3bccf884e..f5ab3dd609ff 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -4873,9 +4873,9 @@ You may have it installed on another user account, but it is not available for t if let Some(permission_request_handler) = pending.permission_request_handler { webview_builder = webview_builder.with_permission_handler(move |kind| { - let kind = webview_permissions::map_permission_kind(kind); + let kind = webview_permissions::to_wry_permission_kind(kind); let response = permission_request_handler(kind); - webview_permissions::map_permission_response(response) + webview_permissions::to_wry_permission_response(response) }); } diff --git a/crates/tauri-runtime-wry/src/webview_permissions.rs b/crates/tauri-runtime-wry/src/webview_permissions.rs index 9543d8a74797..379322530040 100644 --- a/crates/tauri-runtime-wry/src/webview_permissions.rs +++ b/crates/tauri-runtime-wry/src/webview_permissions.rs @@ -4,7 +4,7 @@ use tauri_runtime::webview::{PermissionKind, PermissionResponse}; -pub(super) fn map_permission_kind(kind: wry::PermissionKind) -> PermissionKind { +pub(super) fn to_wry_permission_kind(kind: wry::PermissionKind) -> PermissionKind { match kind { wry::PermissionKind::Microphone => PermissionKind::Microphone, wry::PermissionKind::Camera => PermissionKind::Camera, @@ -26,7 +26,7 @@ pub(super) fn map_permission_kind(kind: wry::PermissionKind) -> PermissionKind { } } -pub(super) fn map_permission_response(response: PermissionResponse) -> wry::PermissionResponse { +pub(super) fn to_wry_permission_response(response: PermissionResponse) -> wry::PermissionResponse { match response { PermissionResponse::Allow => wry::PermissionResponse::Allow, PermissionResponse::Deny => wry::PermissionResponse::Deny, From 33855d9b3780c7af05f23b410364af81f52a3c83 Mon Sep 17 00:00:00 2001 From: Tony Date: Mon, 3 Aug 2026 10:08:28 +0800 Subject: [PATCH 5/7] from not to --- crates/tauri-runtime-wry/src/lib.rs | 2 +- crates/tauri-runtime-wry/src/webview_permissions.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/tauri-runtime-wry/src/lib.rs b/crates/tauri-runtime-wry/src/lib.rs index ec8559eb04a1..d6a1c6f9a40a 100644 --- a/crates/tauri-runtime-wry/src/lib.rs +++ b/crates/tauri-runtime-wry/src/lib.rs @@ -4883,7 +4883,7 @@ You may have it installed on another user account, but it is not available for t if let Some(permission_request_handler) = pending.permission_request_handler { webview_builder = webview_builder.with_permission_handler(move |kind| { - let kind = webview_permissions::to_wry_permission_kind(kind); + let kind = webview_permissions::from_wry_permission_kind(kind); let response = permission_request_handler(kind); webview_permissions::to_wry_permission_response(response) }); diff --git a/crates/tauri-runtime-wry/src/webview_permissions.rs b/crates/tauri-runtime-wry/src/webview_permissions.rs index 379322530040..98e1f57b57db 100644 --- a/crates/tauri-runtime-wry/src/webview_permissions.rs +++ b/crates/tauri-runtime-wry/src/webview_permissions.rs @@ -4,7 +4,7 @@ use tauri_runtime::webview::{PermissionKind, PermissionResponse}; -pub(super) fn to_wry_permission_kind(kind: wry::PermissionKind) -> PermissionKind { +pub(super) fn from_wry_permission_kind(kind: wry::PermissionKind) -> PermissionKind { match kind { wry::PermissionKind::Microphone => PermissionKind::Microphone, wry::PermissionKind::Camera => PermissionKind::Camera, From 1b25d4096ee504bdb3e00e3590e144a6bece5a8c Mon Sep 17 00:00:00 2001 From: Tony Date: Mon, 3 Aug 2026 10:08:39 +0800 Subject: [PATCH 6/7] add docs about the fall through --- crates/tauri/src/app.rs | 3 +++ crates/tauri/src/webview/mod.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/crates/tauri/src/app.rs b/crates/tauri/src/app.rs index b0ae6fc60a18..244ca3234be6 100644 --- a/crates/tauri/src/app.rs +++ b/crates/tauri/src/app.rs @@ -1791,6 +1791,9 @@ tauri::Builder::default() /// The handler receives the [`crate::webview::PermissionKind`] and should return /// the desired [`crate::webview::PermissionResponse`]. /// + /// This is not called if a [`crate::webview::WebviewBuilder::on_permission_request`] + /// is set on the webview and returned a response other than [`crate::webview::PermissionResponse::Default`]. + /// /// > [!NOTE] /// > This handler only triggers for new permission requests. If the user has already /// > allowed or denied a permission persistently within the webview, the browser diff --git a/crates/tauri/src/webview/mod.rs b/crates/tauri/src/webview/mod.rs index f4ef9468241c..e5a3c9bea256 100644 --- a/crates/tauri/src/webview/mod.rs +++ b/crates/tauri/src/webview/mod.rs @@ -705,6 +705,9 @@ tauri::Builder::default() /// The handler receives the [`PermissionKind`] and should return /// the desired [`PermissionResponse`]. /// + /// This is called before [`crate::Builder::on_permission_request`], + /// and skips it if the returned response is not [`crate::webview::PermissionResponse::Default`]. + /// /// > [!NOTE] /// > This handler only triggers for new permission requests. If the user has already /// > allowed or denied a permission persistently within the webview, the browser From 7efb1bf428a6bb6af05d29f1cf5659673c52885a Mon Sep 17 00:00:00 2001 From: Tony Date: Mon, 3 Aug 2026 10:10:28 +0800 Subject: [PATCH 7/7] update change file --- .changes/permission-handler.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.changes/permission-handler.md b/.changes/permission-handler.md index 7a443d104e16..00da137d5370 100644 --- a/.changes/permission-handler.md +++ b/.changes/permission-handler.md @@ -1,11 +1,9 @@ --- -"tauri": "patch:feat" -"tauri-runtime": "patch:feat" -"tauri-runtime-wry": "patch:feat" +"tauri": "minor:feat" +"tauri-runtime": "minor:feat" +"tauri-runtime-wry": "minor:feat" --- Expose the `wry` permission handler API through Tauri. This includes support for permission types such as `DisplayCapture`, `Midi`, `Sensors`, `MediaKeySystemAccess`, `LocalFonts`, `WindowManagement`, `PointerLock`, `AutomaticDownloads`, `FileSystemAccess`, and `Autoplay`. Added `PermissionResponse::{Allow, Deny, Default}` for runtime permission decisions. -Added Android support for geolocation, microphone, camera, protected media, and MIDI requests via JNI. -Updated Linux DisplayCapture support for WebKitGTK versions older than 2.42.