use std::{cell::RefCell, collections::HashMap, rc::Rc}; use ely_domain::{ProfileId, SiteOrigin, SitePermissionFeature}; use servo::WebView; use crate::{PermissionDecision, PermissionRequest}; pub(super) type PermissionStore = Rc>>; #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub(super) struct PermissionKey { profile_id: ProfileId, origin: SiteOrigin, feature: SitePermissionFeature, } impl PermissionKey { fn new(profile_id: ProfileId, origin: SiteOrigin, feature: SitePermissionFeature) -> Self { Self { profile_id, origin, feature } } } pub(super) fn set_permission_decision( permissions: &PermissionStore, request: PermissionRequest, decision: PermissionDecision, ) { permissions .borrow_mut() .insert(PermissionKey::new(request.profile_id, request.origin, request.feature), decision); } pub(super) fn permission_decision_for_webview( permissions: &PermissionStore, profile_id: &ProfileId, webview: &WebView, fallback_url: Option, servo_feature: servo::PermissionFeature, ) -> Option { let origin = site_origin_for_webview(webview, fallback_url)?; let feature = site_permission_feature_for_servo(servo_feature)?; take_permission_decision(permissions, profile_id, origin, feature) } fn take_permission_decision( permissions: &PermissionStore, profile_id: &ProfileId, origin: SiteOrigin, feature: SitePermissionFeature, ) -> Option { let key = PermissionKey::new(profile_id.clone(), origin, feature); let mut permissions = permissions.borrow_mut(); match permissions.get(&key).cloned() { Some(PermissionDecision::AllowOnce) => permissions.remove(&key), decision => decision, } } fn site_origin_for_webview(webview: &WebView, fallback_url: Option) -> Option { webview .url() .map(|url| url.to_string()) .or(fallback_url) .and_then(|url| SiteOrigin::parse(url).ok()) } fn site_permission_feature_for_servo( feature: servo::PermissionFeature, ) -> Option { match feature { servo::PermissionFeature::Geolocation => Some(SitePermissionFeature::Location), servo::PermissionFeature::Notifications => Some(SitePermissionFeature::Notifications), servo::PermissionFeature::Camera => Some(SitePermissionFeature::Camera), servo::PermissionFeature::Microphone => Some(SitePermissionFeature::Microphone), servo::PermissionFeature::PersistentStorage => { Some(SitePermissionFeature::StoragePersistence) } servo::PermissionFeature::Push | servo::PermissionFeature::Midi | servo::PermissionFeature::Speaker | servo::PermissionFeature::DeviceInfo | servo::PermissionFeature::BackgroundSync | servo::PermissionFeature::Bluetooth => None, } } #[cfg(test)] mod tests { use ely_domain::{ProfileId, SiteOrigin, SitePermissionFeature}; use crate::{PermissionDecision, PermissionRequest}; use super::{PermissionStore, set_permission_decision, take_permission_decision}; #[test] fn allow_once_is_consumed_after_one_matching_origin_request() -> Result<(), Box> { let permissions = PermissionStore::default(); let profile_id = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com/path")?; set_permission_decision( &permissions, PermissionRequest { webview_id: ely_domain::WebViewId::new(), profile_id: profile_id.clone(), origin: origin.clone(), feature: SitePermissionFeature::Camera, }, PermissionDecision::AllowOnce, ); assert_eq!( take_permission_decision( &permissions, &profile_id, origin.clone(), SitePermissionFeature::Camera, ), Some(PermissionDecision::AllowOnce) ); assert_eq!( take_permission_decision( &permissions, &profile_id, origin, SitePermissionFeature::Camera ), None ); Ok(()) } #[test] fn allow_always_stays_scoped_to_profile_origin_and_feature() -> Result<(), Box> { let permissions = PermissionStore::default(); let profile_id = ProfileId::new(); let other_profile_id = ProfileId::new(); let origin = SiteOrigin::parse("https://example.com")?; let other_origin = SiteOrigin::parse("https://example.org")?; set_permission_decision( &permissions, PermissionRequest { webview_id: ely_domain::WebViewId::new(), profile_id: profile_id.clone(), origin: origin.clone(), feature: SitePermissionFeature::Notifications, }, PermissionDecision::AllowAlways, ); assert_eq!( take_permission_decision( &permissions, &profile_id, origin.clone(), SitePermissionFeature::Notifications, ), Some(PermissionDecision::AllowAlways) ); assert_eq!( take_permission_decision( &permissions, &other_profile_id, origin, SitePermissionFeature::Notifications, ), None ); assert_eq!( take_permission_decision( &permissions, &profile_id, other_origin, SitePermissionFeature::Notifications, ), None ); assert_eq!( take_permission_decision( &permissions, &profile_id, SiteOrigin::parse("https://example.com")?, SitePermissionFeature::Camera, ), None ); Ok(()) } }