use std::sync::Arc;
use cef::sys::cef_media_access_permission_types_t as MediaPermissionType;
use cef::sys::cef_permission_request_types_t as PermissionType;
use cef::*;
use tauri_runtime::webview::{PermissionKind, PermissionResponse};
pub(crate) type PermissionRequestHandler =
dyn Fn(PermissionKind) -> PermissionResponse + Send + Sync;
const AUDIO_CAPTURE: u32 = MediaPermissionType::CEF_MEDIA_PERMISSION_DEVICE_AUDIO_CAPTURE as u32;
const VIDEO_CAPTURE: u32 = MediaPermissionType::CEF_MEDIA_PERMISSION_DEVICE_VIDEO_CAPTURE as u32;
const DESKTOP_AUDIO_CAPTURE: u32 =
MediaPermissionType::CEF_MEDIA_PERMISSION_DESKTOP_AUDIO_CAPTURE as u32;
const DESKTOP_VIDEO_CAPTURE: u32 =
MediaPermissionType::CEF_MEDIA_PERMISSION_DESKTOP_VIDEO_CAPTURE as u32;
const DESKTOP_CAPTURE: u32 = DESKTOP_AUDIO_CAPTURE | DESKTOP_VIDEO_CAPTURE;
const ALLOY_MEDIA_PERMISSIONS: u32 = AUDIO_CAPTURE | VIDEO_CAPTURE;
const PERMISSION_KINDS: &[(u32, PermissionKind)] = &[
(
PermissionType::CEF_PERMISSION_TYPE_CAMERA_PAN_TILT_ZOOM as u32,
PermissionKind::Camera,
),
(
PermissionType::CEF_PERMISSION_TYPE_CAMERA_STREAM as u32,
PermissionKind::Camera,
),
(
PermissionType::CEF_PERMISSION_TYPE_MIC_STREAM as u32,
PermissionKind::Microphone,
),
(
PermissionType::CEF_PERMISSION_TYPE_CAPTURED_SURFACE_CONTROL as u32,
PermissionKind::DisplayCapture,
),
(
PermissionType::CEF_PERMISSION_TYPE_GEOLOCATION as u32,
PermissionKind::Geolocation,
),
(
PermissionType::CEF_PERMISSION_TYPE_NOTIFICATIONS as u32,
PermissionKind::Notifications,
),
(
PermissionType::CEF_PERMISSION_TYPE_CLIPBOARD as u32,
PermissionKind::ClipboardRead,
),
(
PermissionType::CEF_PERMISSION_TYPE_MIDI_SYSEX as u32,
PermissionKind::Midi,
),
(
PermissionType::CEF_PERMISSION_TYPE_SENSORS as u32,
PermissionKind::Sensors,
),
(
PermissionType::CEF_PERMISSION_TYPE_LOCAL_FONTS as u32,
PermissionKind::LocalFonts,
),
(
PermissionType::CEF_PERMISSION_TYPE_WINDOW_MANAGEMENT as u32,
PermissionKind::WindowManagement,
),
(
PermissionType::CEF_PERMISSION_TYPE_POINTER_LOCK as u32,
PermissionKind::PointerLock,
),
(
PermissionType::CEF_PERMISSION_TYPE_MULTIPLE_DOWNLOADS as u32,
PermissionKind::AutomaticDownloads,
),
(
PermissionType::CEF_PERMISSION_TYPE_FILE_SYSTEM_ACCESS as u32,
PermissionKind::FileSystemAccess,
),
(
PermissionType::CEF_PERMISSION_TYPE_PROTECTED_MEDIA_IDENTIFIER as u32,
PermissionKind::MediaKeySystemAccess,
),
];
const MEDIA_PERMISSION_KINDS: &[(u32, PermissionKind)] = &[
(AUDIO_CAPTURE, PermissionKind::Microphone),
(VIDEO_CAPTURE, PermissionKind::Camera),
(DESKTOP_AUDIO_CAPTURE, PermissionKind::DisplayCapture),
(DESKTOP_VIDEO_CAPTURE, PermissionKind::DisplayCapture),
];
enum AppDecision {
Denied,
Allowed,
NoOpinion,
}
wrap_permission_handler! {
pub struct TauriCefPermissionHandler {
permission_request_handler: Option<Arc<PermissionRequestHandler>>,
}
impl PermissionHandler {
fn on_request_media_access_permission(
&self,
browser: Option<&mut Browser>,
_frame: Option<&mut Frame>,
requesting_origin: Option<&CefString>,
requested_permissions: u32,
callback: Option<&mut MediaAccessCallback>,
) -> ::std::os::raw::c_int {
let host = browser_host(browser);
match self.decide(requested_permissions, media_permission_kind) {
AppDecision::Denied => {
let Some(callback) = callback else {
return 0;
};
callback.cont(0);
1
}
AppDecision::Allowed => {
if grants_desktop_capture(requested_permissions) {
return 0;
}
let Some(callback) = callback else {
return 0;
};
allow_content_settings(
host.as_ref(),
requesting_origin,
&media_content_settings(requested_permissions),
);
callback.cont(requested_permissions);
1
}
AppDecision::NoOpinion => {
if !is_alloy_style(host.as_ref()) {
return 0;
}
if grants_desktop_capture(requested_permissions) {
return 0;
}
let Some(callback) = callback else {
return 0;
};
let allowed = requested_permissions & ALLOY_MEDIA_PERMISSIONS;
if allowed == 0 {
return 0;
}
allow_content_settings(
host.as_ref(),
requesting_origin,
&media_content_settings(allowed),
);
callback.cont(allowed);
1
}
}
}
fn on_show_permission_prompt(
&self,
browser: Option<&mut Browser>,
_prompt_id: u64,
_requesting_origin: Option<&CefString>,
requested_permissions: u32,
callback: Option<&mut PermissionPromptCallback>,
) -> ::std::os::raw::c_int {
let host = browser_host(browser);
match self.decide(requested_permissions, permission_kind) {
AppDecision::Denied => {
let Some(callback) = callback else {
return 0;
};
callback.cont(PermissionRequestResult::DENY);
1
}
AppDecision::Allowed => {
let Some(callback) = callback else {
return 0;
};
callback.cont(PermissionRequestResult::ACCEPT);
1
}
AppDecision::NoOpinion => {
if !is_alloy_style(host.as_ref()) {
return 0;
}
let Some(callback) = callback else {
return 0;
};
callback.cont(PermissionRequestResult::ACCEPT);
1
}
}
}
}
}
impl TauriCefPermissionHandler {
fn decide(&self, requested: u32, kind: fn(u32) -> PermissionKind) -> AppDecision {
let Some(handler) = &self.permission_request_handler else {
return AppDecision::NoOpinion;
};
let mut asked = false;
let mut allowed_all = true;
for permission in requested_permissions(requested) {
asked = true;
match handler(kind(permission)) {
PermissionResponse::Deny => return AppDecision::Denied,
PermissionResponse::Allow => {}
PermissionResponse::Default => allowed_all = false,
}
}
if asked && allowed_all {
AppDecision::Allowed
} else {
AppDecision::NoOpinion
}
}
}
fn grants_desktop_capture(requested: u32) -> bool {
requested & DESKTOP_CAPTURE != 0
}
fn requested_permissions(requested: u32) -> impl Iterator<Item = u32> {
(0..u32::BITS)
.map(move |bit| requested & (1 << bit))
.filter(|permission| *permission != 0)
}
fn permission_kind(permission: u32) -> PermissionKind {
lookup_permission_kind(PERMISSION_KINDS, permission)
}
fn media_permission_kind(permission: u32) -> PermissionKind {
lookup_permission_kind(MEDIA_PERMISSION_KINDS, permission)
}
fn lookup_permission_kind(table: &[(u32, PermissionKind)], permission: u32) -> PermissionKind {
table
.iter()
.find(|(candidate, _)| *candidate == permission)
.map(|(_, kind)| *kind)
.unwrap_or(PermissionKind::Other)
}
fn media_content_settings(granted: u32) -> Vec<ContentSettingTypes> {
let mut settings = Vec::with_capacity(2);
if granted & AUDIO_CAPTURE != 0 {
settings.push(ContentSettingTypes::MEDIASTREAM_MIC);
}
if granted & VIDEO_CAPTURE != 0 {
settings.push(ContentSettingTypes::MEDIASTREAM_CAMERA);
}
settings
}
fn browser_host(browser: Option<&mut Browser>) -> Option<BrowserHost> {
browser.and_then(|browser| browser.host())
}
fn is_alloy_style(host: Option<&BrowserHost>) -> bool {
host.is_some_and(|host| host.runtime_style() == RuntimeStyle::ALLOY)
}
fn allow_content_settings(
host: Option<&BrowserHost>,
requesting_origin: Option<&CefString>,
settings: &[ContentSettingTypes],
) {
if requesting_origin.is_none() || settings.is_empty() {
return;
}
let Some(context) = host.and_then(|host| host.request_context()) else {
return;
};
for setting in settings {
context.set_content_setting(
requesting_origin,
None,
*setting,
ContentSettingValues::ALLOW,
);
}
}