oxide_browser/
permissions.rs1use std::collections::HashMap;
17use std::sync::{Arc, Mutex};
18
19pub const PERMISSION_PENDING: i32 = -5;
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27pub enum PermissionKind {
28 Camera,
29 Microphone,
30 Geolocation,
31 ScreenCapture,
32}
33
34impl PermissionKind {
35 pub fn description(&self) -> &'static str {
37 match self {
38 PermissionKind::Camera => "Use your camera",
39 PermissionKind::Microphone => "Use your microphone",
40 PermissionKind::Geolocation => "Know your location",
41 PermissionKind::ScreenCapture => "See your screen",
42 }
43 }
44
45 pub fn name(&self) -> &'static str {
47 match self {
48 PermissionKind::Camera => "camera",
49 PermissionKind::Microphone => "microphone",
50 PermissionKind::Geolocation => "geolocation",
51 PermissionKind::ScreenCapture => "screen-capture",
52 }
53 }
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58pub enum PermissionStatus {
59 Granted,
61 Denied,
63 Pending,
65}
66
67#[derive(Clone, Debug)]
69pub struct PendingPermission {
70 pub origin: String,
72 pub kind: PermissionKind,
74}
75
76#[derive(Default)]
78pub struct PermissionsState {
79 decisions: HashMap<(String, PermissionKind), bool>,
81 pub pending: Option<PendingPermission>,
84}
85
86pub type SharedPermissions = Arc<Mutex<PermissionsState>>;
88
89pub fn check_or_request(
94 perms: &SharedPermissions,
95 origin: &str,
96 kind: PermissionKind,
97) -> PermissionStatus {
98 let mut state = perms.lock().unwrap();
99 if let Some(&allowed) = state.decisions.get(&(origin.to_string(), kind)) {
100 return if allowed {
101 PermissionStatus::Granted
102 } else {
103 PermissionStatus::Denied
104 };
105 }
106 if state.pending.is_none() {
107 state.pending = Some(PendingPermission {
108 origin: origin.to_string(),
109 kind,
110 });
111 }
112 PermissionStatus::Pending
113}
114
115pub fn resolve_pending(perms: &SharedPermissions, allow: bool) {
119 let mut state = perms.lock().unwrap();
120 if let Some(req) = state.pending.take() {
121 state.decisions.insert((req.origin, req.kind), allow);
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 fn shared() -> SharedPermissions {
130 Arc::new(Mutex::new(PermissionsState::default()))
131 }
132
133 #[test]
134 fn first_request_is_pending_and_queues_prompt() {
135 let perms = shared();
136 let status = check_or_request(&perms, "https://a.com", PermissionKind::Camera);
137 assert_eq!(status, PermissionStatus::Pending);
138 let state = perms.lock().unwrap();
139 let pending = state.pending.as_ref().expect("prompt queued");
140 assert_eq!(pending.origin, "https://a.com");
141 assert_eq!(pending.kind, PermissionKind::Camera);
142 }
143
144 #[test]
145 fn allow_then_granted() {
146 let perms = shared();
147 check_or_request(&perms, "https://a.com", PermissionKind::Camera);
148 resolve_pending(&perms, true);
149 assert_eq!(
150 check_or_request(&perms, "https://a.com", PermissionKind::Camera),
151 PermissionStatus::Granted
152 );
153 }
154
155 #[test]
156 fn block_then_denied() {
157 let perms = shared();
158 check_or_request(&perms, "https://a.com", PermissionKind::Microphone);
159 resolve_pending(&perms, false);
160 assert_eq!(
161 check_or_request(&perms, "https://a.com", PermissionKind::Microphone),
162 PermissionStatus::Denied
163 );
164 }
165
166 #[test]
167 fn decisions_are_scoped_per_origin_and_kind() {
168 let perms = shared();
169 check_or_request(&perms, "https://a.com", PermissionKind::Camera);
170 resolve_pending(&perms, true);
171 assert_eq!(
173 check_or_request(&perms, "https://a.com", PermissionKind::Microphone),
174 PermissionStatus::Pending
175 );
176 resolve_pending(&perms, false);
177 assert_eq!(
179 check_or_request(&perms, "https://b.com", PermissionKind::Camera),
180 PermissionStatus::Pending
181 );
182 }
183
184 #[test]
185 fn second_request_waits_for_active_prompt() {
186 let perms = shared();
187 check_or_request(&perms, "https://a.com", PermissionKind::Camera);
188 assert_eq!(
190 check_or_request(&perms, "https://a.com", PermissionKind::Geolocation),
191 PermissionStatus::Pending
192 );
193 assert_eq!(
194 perms.lock().unwrap().pending.as_ref().unwrap().kind,
195 PermissionKind::Camera
196 );
197 resolve_pending(&perms, true);
198 assert_eq!(
200 check_or_request(&perms, "https://a.com", PermissionKind::Geolocation),
201 PermissionStatus::Pending
202 );
203 assert_eq!(
204 perms.lock().unwrap().pending.as_ref().unwrap().kind,
205 PermissionKind::Geolocation
206 );
207 }
208
209 #[test]
210 fn resolve_without_pending_is_noop() {
211 let perms = shared();
212 resolve_pending(&perms, true);
213 assert!(perms.lock().unwrap().pending.is_none());
214 }
215}