Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f884930
feat: add permission handler API - implement cross-platform permissio…
F0RLE Feb 23, 2026
33e6332
fix(wkwebview): remove leftover PermissionObserver from UIDelegate si…
F0RLE Feb 24, 2026
2013b25
Fix `permission_handler` example on linux
Legend-Master Feb 24, 2026
2c44f16
Use type instead of string check
Legend-Master Feb 24, 2026
f213859
Add more platform specific notes
Legend-Master Feb 24, 2026
a0133a8
Move permissions to a file
Legend-Master Feb 24, 2026
09814aa
Add notice about prompt not supported on linux
Legend-Master Feb 24, 2026
84e01f4
fix(webkitgtk): handle combined audio+video permissions and use is_fo…
F0RLE Apr 6, 2026
110a347
chore: merge upstream dev and resolve conflicts
F0RLE Apr 6, 2026
911de41
fix: reorder module declarations for rustfmt
F0RLE Apr 7, 2026
47afa95
fix(android): scope permission handler per webview
F0RLE Apr 9, 2026
b9d86ef
fix(android): honor permission handler for geolocation
F0RLE Apr 9, 2026
e25436d
fix(android): avoid overgranting mixed permission requests
F0RLE Apr 23, 2026
954e3e6
fix(android): scope permission callbacks by webview
F0RLE Apr 25, 2026
88472ae
fix(android): pass existing webview id string
F0RLE Apr 25, 2026
26ca4b5
Merge branch 'dev' into feat/permission-handler
Legend-Master Jun 10, 2026
f00e365
Migrate `PACKAGE.get().unwrap()`
Legend-Master Jun 10, 2026
059df14
Fix `web_chrome_client`
Legend-Master Jun 10, 2026
1cadf91
small refactor on permissionLauncher
Legend-Master Jun 11, 2026
fe9806d
Add `PermissionResponse::Default` docs for all platforms
Legend-Master Jun 11, 2026
cced03c
fix(android): simplify permission JNI response
F0RLE Jun 11, 2026
342d4f1
document allow is not supported on android
Legend-Master Jun 11, 2026
30aa70f
refactor: simplify permission responses
F0RLE Jun 11, 2026
7f8a188
fix: update permission handler example
F0RLE Jun 11, 2026
dc2766c
fix(android): pass unknown permissions to handler
F0RLE Jun 11, 2026
f91f672
fix(android): allow explicit unknown permissions
F0RLE Jun 11, 2026
373ca8a
onPermissionRequestNative will not return other values
Legend-Master Jun 11, 2026
e7c84dc
Send individual permissions to rust side
Legend-Master Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changes/permission-handler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"wry": minor
---

Add an expanded permission handling API for WebView2, WKWebView, WebKitGTK, and Android.
This includes:
- `PermissionKind` expansion: `DisplayCapture`, `Midi`, `Sensors`, `MediaKeySystemAccess`, `LocalFonts`, `WindowManagement`, `PointerLock`, `AutomaticDownloads`, `FileSystemAccess`, `Autoplay`.
- Support for `PermissionResponse::Prompt` to trigger native system dialogs.
- Android support via JNI bridge (`onPermissionRequest` in `RustWebChromeClient`).
- macOS: Split camera/microphone requests; `CameraAndMicrophone` resolved from individual responses.
- Linux: `DisplayCapture` detection for WebKitGTK < 2.42 (getDisplayMedia fix).
- Windows: Full coverage of all 12 `COREWEBVIEW2_PERMISSION_KIND` values.
66 changes: 66 additions & 0 deletions examples/permission_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

//! Example demonstrating the permission handler API.
//!
//! Run: cargo run --example permission_handler
//! Then click the buttons and watch the terminal output.

fn main() -> wry::Result<()> {
use tao::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
use wry::{PermissionKind, PermissionResponse, WebViewBuilder};

let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title("Permission Handler Example")
.with_inner_size(tao::dpi::LogicalSize::new(800, 600))
.build(&event_loop)
.unwrap();

let builder = WebViewBuilder::new()
.with_url("https://permission.site/")
.with_permission_handler(|kind| {
let response = match kind {
PermissionKind::Geolocation => PermissionResponse::Prompt,
_ => PermissionResponse::Allow,
};
println!("[permission] {kind} → {response}");
response
});

#[cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
))]
let _webview = builder.build(&window)?;
#[cfg(not(any(
target_os = "windows",
target_os = "macos",
target_os = "ios",
target_os = "android"
)))]
let _webview = {
use tao::platform::unix::WindowExtUnix;
use wry::WebViewBuilderExtUnix;
let vbox = window.default_vbox().unwrap();
builder.build_gtk(vbox)?
};

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
if let Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} = event
{
*control_flow = ControlFlow::Exit;
}
});
}
90 changes: 87 additions & 3 deletions src/android/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ use std::os::fd::{AsFd, AsRawFd};

use super::{
main_pipe::{MainPipe, MAIN_PIPE},
ASSET_LOADER_DOMAIN, EVAL_CALLBACKS, IPC, ON_LOAD_HANDLER, REQUEST_HANDLER, TITLE_CHANGE_HANDLER,
URL_LOADING_OVERRIDE, WITH_ASSET_LOADER,
ASSET_LOADER_DOMAIN, EVAL_CALLBACKS, IPC, ON_LOAD_HANDLER, PERMISSION_HANDLER, REQUEST_HANDLER,
TITLE_CHANGE_HANDLER, URL_LOADING_OVERRIDE, WITH_ASSET_LOADER,
};

use crate::PageLoadEvent;
use crate::{PageLoadEvent, PermissionKind, PermissionResponse};

#[macro_export]
macro_rules! android_binding {
Expand Down Expand Up @@ -87,6 +87,22 @@ macro_rules! android_binding {
handleReceivedTitle,
[JString, JString],
);
android_fn!(
$domain,
$package,
RustWebChromeClient,
onPermissionRequestNative,
[JString, jni::objects::JObjectArray],
jboolean
);
android_fn!(
$domain,
$package,
RustWebChromeClient,
onGeolocationPermissionRequestNative,
[JString, JString],
jboolean
);
}};
}

Expand Down Expand Up @@ -485,3 +501,71 @@ pub unsafe fn onPageLoaded(mut env: JNIEnv, _: JClass, webview_id: JString, url:
}
}
}

#[allow(non_snake_case)]
pub unsafe fn onPermissionRequestNative(
mut env: JNIEnv,
_: JClass,
webview_id: JString,
resources: jni::objects::JObjectArray,
) -> jboolean {
let mut denied = false;
let Ok(webview_id) = env.get_string(&webview_id) else {
return false.into();
};
let webview_id = webview_id.to_str().ok().unwrap_or_default();
let permission_handlers = PERMISSION_HANDLER.lock().unwrap();
let Some(handler) = permission_handlers.get(webview_id) else {
return false.into();
};

if let Ok(size) = env.get_array_length(&resources) {
for i in 0..size {
if let Ok(resource) = env.get_object_array_element(&resources, i) {
if let Ok(resource_str) = env.get_string(&resource.into()) {
let resource_str = resource_str.to_string_lossy();

let kind = match resource_str.as_ref() {
"android.webkit.resource.AUDIO_CAPTURE" => PermissionKind::Microphone,
"android.webkit.resource.VIDEO_CAPTURE" => PermissionKind::Camera,
"android.webkit.resource.PROTECTED_MEDIA_ID" => PermissionKind::MediaKeySystemAccess,
"android.webkit.resource.MIDI_SYSEX" => PermissionKind::Midi,
_ => PermissionKind::Other,
};

match (handler.handler)(kind) {
PermissionResponse::Deny => denied = true,
PermissionResponse::Allow
| PermissionResponse::Default
| PermissionResponse::Prompt => {}
}
}
}
}
}

denied.into()
}

#[allow(non_snake_case)]
pub unsafe fn onGeolocationPermissionRequestNative(
mut env: JNIEnv,
_: JClass,
webview_id: JString,
_origin: JString,
) -> jboolean {
let Ok(webview_id) = env.get_string(&webview_id) else {
return false.into();
};
let webview_id = webview_id.to_str().ok().unwrap_or_default();
let permission_handlers = PERMISSION_HANDLER.lock().unwrap();
let Some(handler) = permission_handlers.get(webview_id) else {
return false.into();
};

matches!(
(handler.handler)(PermissionKind::Geolocation),
PermissionResponse::Deny
)
.into()
}
Loading