Skip to content

Commit fa4bba7

Browse files
committed
feat: add permission handler API - implement cross-platform permission handler API, fix combined camera and microphone logic on macOS, update documentation to clarify NFC and Bluetooth support, and simplify permission_handler example using permission.site
1 parent e430558 commit fa4bba7

10 files changed

Lines changed: 470 additions & 24 deletions

File tree

.changes/permission-handler.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"wry": minor
3+
---
4+
5+
Add an expanded permission handling API for WebView2, WKWebView, WebKitGTK, and Android.
6+
This includes:
7+
- `PermissionKind` expansion: `DisplayCapture`, `Midi`, `Nfc`, `Bluetooth`, `Sensors`, `MediaKeySystemAccess`, `LocalFonts`, `WindowManagement`, `PointerLock`, `AutomaticDownloads`, `FileSystemAccess`, `Autoplay`.
8+
- Support for `PermissionResponse::Prompt` to trigger native system dialogs.
9+
- Android support (experimental) via JNI bridge.
10+
- macOS: Split camera/microphone requests with secure Default behavior (Prompt instead of auto-grant).
11+
- Linux: `DisplayCapture` detection for WebKitGTK < 2.42 (getDisplayMedia fix).
12+
- Windows: Full coverage of all 12 `COREWEBVIEW2_PERMISSION_KIND` values.

examples/permission_handler.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
2+
// SPDX-License-Identifier: Apache-2.0
3+
// SPDX-License-Identifier: MIT
4+
5+
//! Example demonstrating the permission handler API.
6+
//!
7+
//! Run: cargo run --example permission_handler
8+
//! Then click the buttons and watch the terminal output.
9+
10+
fn main() -> wry::Result<()> {
11+
use tao::{
12+
event::{Event, WindowEvent},
13+
event_loop::{ControlFlow, EventLoop},
14+
window::WindowBuilder,
15+
};
16+
use wry::{PermissionKind, PermissionResponse, WebViewBuilder};
17+
18+
let event_loop = EventLoop::new();
19+
let window = WindowBuilder::new()
20+
.with_title("Permission Handler Example")
21+
.with_inner_size(tao::dpi::LogicalSize::new(800, 600))
22+
.build(&event_loop)
23+
.unwrap();
24+
25+
let _webview = WebViewBuilder::new()
26+
.with_url("https://permission.site/")
27+
.with_permission_handler(|kind| {
28+
let response = match kind {
29+
PermissionKind::Geolocation => PermissionResponse::Prompt,
30+
_ => PermissionResponse::Allow,
31+
};
32+
println!("[permission] {kind} → {response}");
33+
response
34+
})
35+
.build(&window)?;
36+
37+
event_loop.run(move |event, _, control_flow| {
38+
*control_flow = ControlFlow::Wait;
39+
if let Event::WindowEvent {
40+
event: WindowEvent::CloseRequested,
41+
..
42+
} = event
43+
{
44+
*control_flow = ControlFlow::Exit;
45+
}
46+
});
47+
}

src/android/binding.rs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ pub use jni::{
1616
pub use ndk;
1717

1818
use super::{
19-
ASSET_LOADER_DOMAIN, EVAL_CALLBACKS, IPC, ON_LOAD_HANDLER, REQUEST_HANDLER, TITLE_CHANGE_HANDLER,
20-
URL_LOADING_OVERRIDE, WITH_ASSET_LOADER,
19+
ASSET_LOADER_DOMAIN, EVAL_CALLBACKS, IPC, ON_LOAD_HANDLER, PERMISSION_HANDLER, REQUEST_HANDLER,
20+
TITLE_CHANGE_HANDLER, URL_LOADING_OVERRIDE, WITH_ASSET_LOADER,
2121
};
2222

23-
use crate::PageLoadEvent;
23+
use crate::{PageLoadEvent, PermissionKind, PermissionResponse};
2424

2525
#[macro_export]
2626
macro_rules! android_binding {
@@ -97,6 +97,14 @@ macro_rules! android_binding {
9797
handleReceivedTitle,
9898
[JObject, JString],
9999
);
100+
android_fn!(
101+
$domain,
102+
$package,
103+
RustWebChromeClient,
104+
onPermissionRequestNative,
105+
[jni::objects::JObjectArray],
106+
jint
107+
);
100108
}};
101109
}
102110

@@ -413,3 +421,51 @@ pub unsafe fn onPageLoaded(mut env: JNIEnv, _: JClass, url: JString) {
413421
}
414422
}
415423
}
424+
425+
pub unsafe fn onPermissionRequestNative(
426+
mut env: JNIEnv,
427+
_: JClass,
428+
resources: jni::objects::JObjectArray,
429+
) -> jint {
430+
let mut allowed = false;
431+
let mut denied = false;
432+
let mut prompt = false;
433+
434+
if let Ok(size) = env.get_array_length(&resources) {
435+
for i in 0..size {
436+
if let Ok(resource) = env.get_object_array_element(&resources, i) {
437+
if let Ok(resource_str) = env.get_string(&resource.into()) {
438+
let resource_str = resource_str.to_string_lossy();
439+
440+
let kind = match resource_str.as_ref() {
441+
"android.webkit.resource.AUDIO_CAPTURE" => PermissionKind::Microphone,
442+
"android.webkit.resource.VIDEO_CAPTURE" => PermissionKind::Camera,
443+
"android.webkit.resource.PROTECTED_MEDIA_ID" => PermissionKind::MediaKeySystemAccess,
444+
"android.webkit.resource.MIDI_SYSEX" => PermissionKind::Midi,
445+
_ => PermissionKind::Other,
446+
};
447+
448+
if let Some(handler) = &*PERMISSION_HANDLER.lock().unwrap() {
449+
match (handler.handler)(kind) {
450+
PermissionResponse::Allow => allowed = true,
451+
PermissionResponse::Deny => denied = true,
452+
PermissionResponse::Prompt => prompt = true,
453+
PermissionResponse::Default => {}
454+
}
455+
}
456+
}
457+
}
458+
}
459+
}
460+
461+
// Consolidated decision logic
462+
if denied {
463+
1 // Deny
464+
} else if allowed {
465+
0 // Allow
466+
} else if prompt {
467+
3 // Prompt
468+
} else {
469+
2 // Default
470+
}
471+
}

src/android/kotlin/RustWebChromeClient.kt

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,24 @@ class RustWebChromeClient(appActivity: WryActivity) : WebChromeClient() {
9292
}
9393

9494
override fun onPermissionRequest(request: PermissionRequest) {
95+
val response = onPermissionRequestNative(request.resources)
96+
when (response) {
97+
0 -> { // Allow
98+
request.grant(request.resources)
99+
return
100+
}
101+
1 -> { // Deny
102+
request.deny()
103+
return
104+
}
105+
2 -> { // Default
106+
// Continue with default logic
107+
}
108+
3 -> { // Prompt
109+
// Continue with default logic (which prompts)
110+
}
111+
}
112+
95113
val isRequestPermissionRequired = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
96114
val permissionList: MutableList<String> = ArrayList()
97115
if (listOf(*request.resources).contains("android.webkit.resource.VIDEO_CAPTURE")) {
@@ -118,6 +136,8 @@ class RustWebChromeClient(appActivity: WryActivity) : WebChromeClient() {
118136
}
119137
}
120138

139+
private external fun onPermissionRequestNative(resources: Array<String>): Int
140+
121141
/**
122142
* Show the browser alert modal
123143
* @param view
@@ -482,12 +502,15 @@ class RustWebChromeClient(appActivity: WryActivity) : WebChromeClient() {
482502
return File.createTempFile(imageFileName, ".jpg", storageDir)
483503
}
484504

485-
override fun onReceivedTitle(
486-
view: WebView,
487-
title: String
488-
) {
489-
handleReceivedTitle(view, title)
505+
override fun onPermissionRequest(request: PermissionRequest) {
506+
val result = onPermissionRequestNative(request.resources)
507+
when (result) {
508+
0 -> request.grant(request.resources)
509+
1 -> request.deny()
510+
else -> super.onPermissionRequest(request)
511+
}
490512
}
491513

514+
private external fun onPermissionRequestNative(resources: Array<String>): Int
492515
private external fun handleReceivedTitle(webview: WebView, title: String)
493516
}

src/android/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
// SPDX-License-Identifier: MIT
44

55
use super::{PageLoadEvent, WebViewAttributes, RGBA};
6-
use crate::{custom_protocol_workaround, RequestAsyncResponder, Result};
6+
use crate::{
7+
custom_protocol_workaround, PermissionKind, PermissionResponse, RequestAsyncResponder, Result,
8+
};
79
use base64::{engine::general_purpose, Engine};
810
use crossbeam_channel::*;
911
use html5ever::{interface::QualName, namespace_url, ns, tendril::TendrilSink, LocalName};
@@ -81,6 +83,7 @@ define_static_handlers! {
8183
TITLE_CHANGE_HANDLER = UnsafeTitleHandler { handler: Box<dyn Fn(String)> };
8284
URL_LOADING_OVERRIDE = UnsafeUrlLoadingOverride { handler: Box<dyn Fn(String) -> bool> };
8385
ON_LOAD_HANDLER = UnsafeOnPageLoadHandler { handler: Box<dyn Fn(PageLoadEvent, String)> };
86+
PERMISSION_HANDLER = UnsafePermissionHandler { handler: Box<dyn Fn(PermissionKind) -> PermissionResponse> };
8487
}
8588

8689
pub static WITH_ASSET_LOADER: StaticValue<Option<bool>> = StaticValue(Mutex::new(None));

0 commit comments

Comments
 (0)