-
-
Notifications
You must be signed in to change notification settings - Fork 405
feat: add permission handler API for WebView2 #1654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from 6 commits
a5848eb
717dfa2
755877e
c2ca7a4
c2cc33d
1c2376a
4fa8e94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -535,6 +535,41 @@ pub struct NewWindowFeatures { | |
| pub opener: NewWindowOpener, | ||
| } | ||
|
|
||
| /// Permission types that can be requested by the webview. | ||
| /// | ||
| /// See [`WebViewBuilder::with_permission_handler`]. | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 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, | ||
| /// Other unrecognized permission type. | ||
| Other, | ||
| } | ||
|
|
||
| /// Response for permission requests. | ||
| /// | ||
| /// See [`WebViewBuilder::with_permission_handler`]. | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] | ||
| pub enum PermissionResponse { | ||
| /// Grant the permission. | ||
| Allow, | ||
| /// Deny the permission. | ||
| Deny, | ||
| /// Use default behavior (show system prompt). | ||
| #[default] | ||
| Default, | ||
| } | ||
|
|
||
| /// An id for a webview | ||
| pub type WebViewId<'a> = &'a str; | ||
|
|
||
|
|
@@ -794,6 +829,33 @@ pub struct WebViewAttributes<'a> { | |
|
|
||
| /// Whether JavaScript should be disabled. | ||
| pub javascript_disabled: bool, | ||
|
|
||
| /// A handler to intercept permission requests from the webview. | ||
| /// | ||
| /// The handler receives the [`PermissionKind`] and should return | ||
| /// the desired [`PermissionResponse`]. | ||
| /// | ||
| /// ## 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**: Not yet implemented, handler is ignored. | ||
| /// | ||
| /// ## Example | ||
| /// | ||
| /// ```no_run | ||
| /// # use wry::{WebViewBuilder, PermissionKind, PermissionResponse}; | ||
| /// let webview = WebViewBuilder::new() | ||
| /// .with_permission_handler(|kind| { | ||
| /// match kind { | ||
| /// PermissionKind::Microphone => PermissionResponse::Allow, | ||
| /// PermissionKind::Camera => PermissionResponse::Allow, | ||
| /// _ => PermissionResponse::Default, | ||
| /// } | ||
| /// }); | ||
| /// ``` | ||
| pub permission_handler: Option<Box<dyn Fn(PermissionKind) -> PermissionResponse + Send + Sync>>, | ||
| } | ||
|
|
||
| impl Default for WebViewAttributes<'_> { | ||
|
|
@@ -836,6 +898,7 @@ impl Default for WebViewAttributes<'_> { | |
| }), | ||
| background_throttling: None, | ||
| javascript_disabled: false, | ||
| permission_handler: None, | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -1260,6 +1323,37 @@ impl<'a> WebViewBuilder<'a> { | |
| self | ||
| } | ||
|
|
||
| /// Set a handler to intercept permission requests from the webview. | ||
| /// | ||
| /// The handler receives the [`PermissionKind`] and should return | ||
| /// the desired [`PermissionResponse`]. | ||
| /// | ||
| /// ## Platform-specific: | ||
| /// | ||
| /// - **Windows**: Fully supported via WebView2's PermissionRequested event. | ||
| /// - **macOS / iOS / Linux / Android**: Not yet implemented, handler is ignored. | ||
|
Comment on lines
+1336
to
+1337
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like you have implemented most of them! |
||
| /// | ||
| /// ## Example | ||
| /// | ||
| /// ```no_run | ||
| /// # use wry::{WebViewBuilder, PermissionKind, PermissionResponse}; | ||
| /// let webview = WebViewBuilder::new() | ||
| /// .with_permission_handler(|kind| { | ||
| /// match kind { | ||
| /// PermissionKind::Microphone => PermissionResponse::Allow, | ||
| /// PermissionKind::Camera => PermissionResponse::Allow, | ||
| /// _ => PermissionResponse::Default, | ||
| /// } | ||
| /// }); | ||
| /// ``` | ||
| pub fn with_permission_handler<F>(mut self, handler: F) -> Self | ||
| where | ||
| F: Fn(PermissionKind) -> PermissionResponse + Send + Sync + 'static, | ||
| { | ||
| self.attrs.permission_handler = Some(Box::new(handler)); | ||
| self | ||
| } | ||
|
|
||
| /// Set a download started handler to manage incoming downloads. | ||
| /// | ||
| /// The closure takes two parameters, the first is a `String` representing the url being downloaded from and and the | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,9 @@ | |
| use std::{cell::RefCell, ptr::null_mut, rc::Rc}; | ||
|
|
||
| use block2::Block; | ||
| #[cfg(target_os = "macos")] | ||
| use objc2::DefinedClass; | ||
| use objc2::{define_class, msg_send, rc::Retained, runtime::NSObject, MainThreadOnly}; | ||
| use objc2::{ | ||
| define_class, msg_send, rc::Retained, runtime::NSObject, DefinedClass, MainThreadOnly, | ||
| }; | ||
| #[cfg(target_os = "macos")] | ||
| use objc2_app_kit::{NSModalResponse, NSModalResponseOK, NSOpenPanel, NSWindowDelegate}; | ||
| use objc2_foundation::{MainThreadMarker, NSObjectProtocol}; | ||
|
|
@@ -21,7 +21,7 @@ use objc2_web_kit::{ | |
| WKFrameInfo, WKMediaCaptureType, WKPermissionDecision, WKSecurityOrigin, WKUIDelegate, | ||
| }; | ||
|
|
||
| use crate::{NewWindowFeatures, NewWindowResponse, WryWebView}; | ||
| use crate::{NewWindowFeatures, NewWindowResponse, PermissionKind, PermissionResponse, WryWebView}; | ||
|
|
||
| #[cfg(target_os = "macos")] | ||
| struct NewWindow { | ||
|
|
@@ -86,6 +86,7 @@ pub struct WryWebViewUIDelegateIvars { | |
| Option<Box<dyn Fn(String, NewWindowFeatures) -> NewWindowResponse + Send + Sync>>, | ||
| #[cfg(target_os = "macos")] | ||
| new_windows: Rc<RefCell<Vec<NewWindow>>>, | ||
| permission_handler: Option<Box<dyn Fn(PermissionKind) -> PermissionResponse + Send + Sync>>, | ||
| } | ||
|
|
||
| define_class!( | ||
|
|
@@ -132,11 +133,30 @@ define_class!( | |
| _webview: &WryWebView, | ||
| _origin: &WKSecurityOrigin, | ||
| _frame: &WKFrameInfo, | ||
| _capture_type: WKMediaCaptureType, | ||
| capture_type: WKMediaCaptureType, | ||
| decision_handler: &Block<dyn Fn(WKPermissionDecision)>, | ||
| ) { | ||
| //https://developer.apple.com/documentation/webkit/wkpermissiondecision?language=objc | ||
| (*decision_handler).call((WKPermissionDecision::Grant,)); | ||
| // Determine permission kind based on capture type | ||
| let permission_kind = match capture_type { | ||
| WKMediaCaptureType::Camera => PermissionKind::Camera, | ||
| WKMediaCaptureType::Microphone => PermissionKind::Microphone, | ||
| WKMediaCaptureType::CameraAndMicrophone => PermissionKind::Microphone, // Treat as microphone for now | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Collapsing Maybe we can add some platform-specific type and document it well, or split it into something like |
||
| _ => PermissionKind::Other, | ||
| }; | ||
|
|
||
| // Call user's permission handler if set | ||
| let decision = if let Some(handler) = &self.ivars().permission_handler { | ||
| match handler(permission_kind) { | ||
| PermissionResponse::Allow => WKPermissionDecision::Grant, | ||
| PermissionResponse::Deny => WKPermissionDecision::Deny, | ||
| PermissionResponse::Default => WKPermissionDecision::Grant, // Default to grant for backwards compatibility | ||
| } | ||
| } else { | ||
| // No handler set, default to grant (backwards compatible behavior) | ||
| WKPermissionDecision::Grant | ||
| }; | ||
|
Comment on lines
148
to
158
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should align the default action with the OS, giving
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to match the old behavior here? I'm not really aware of the historical context here though
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, you're right. Let's leave it granted then. But I think we might need a discussion for this, maybe in a new issue.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added a PermissionResponse::Prompt variant. This keeps the default behavior as Grant (for backward compatibility) but allows apps to explicitly opt-in to the system prompt if they want. I think this covers both use cases. |
||
|
|
||
| (*decision_handler).call((decision,)); | ||
| } | ||
|
|
||
| #[cfg(target_os = "macos")] | ||
|
|
@@ -270,6 +290,7 @@ impl WryWebViewUIDelegate { | |
| new_window_req_handler: Option< | ||
| Box<dyn Fn(String, NewWindowFeatures) -> NewWindowResponse + Send + Sync>, | ||
| >, | ||
| permission_handler: Option<Box<dyn Fn(PermissionKind) -> PermissionResponse + Send + Sync>>, | ||
| ) -> Retained<Self> { | ||
| #[cfg(target_os = "ios")] | ||
| let _new_window_req_handler = new_window_req_handler; | ||
|
|
@@ -281,6 +302,7 @@ impl WryWebViewUIDelegate { | |
| new_window_req_handler, | ||
| #[cfg(target_os = "macos")] | ||
| new_windows: Rc::new(RefCell::new(vec![])), | ||
| permission_handler, | ||
| }); | ||
| unsafe { msg_send![super(delegate), init] } | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.