1use std::rc::Rc;
6
7use dom_struct::dom_struct;
8use embedder_traits::{self, AllowOrDeny, EmbedderMsg, PermissionFeature};
9use js::conversions::ConversionResult;
10use js::jsapi::JSObject;
11use js::jsval::{ObjectValue, UndefinedValue};
12use script_bindings::inheritance::Castable;
13use script_bindings::reflector::{Reflector, reflect_dom_object};
14use servo_base::generic_channel;
15use servo_config::pref;
16
17use super::window::Window;
18use crate::conversions::Convert;
19use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
20 PermissionDescriptor, PermissionName, PermissionState, PermissionStatusMethods,
21};
22use crate::dom::bindings::codegen::Bindings::PermissionsBinding::PermissionsMethods;
23use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
24use crate::dom::bindings::error::Error;
25use crate::dom::bindings::reflector::DomGlobal;
26use crate::dom::bindings::root::DomRoot;
27#[cfg(feature = "bluetooth")]
28use crate::dom::bluetooth::Bluetooth;
29#[cfg(feature = "bluetooth")]
30use crate::dom::bluetoothpermissionresult::BluetoothPermissionResult;
31use crate::dom::globalscope::GlobalScope;
32use crate::dom::permissionstatus::PermissionStatus;
33use crate::dom::promise::Promise;
34use crate::realms::{AlreadyInRealm, InRealm};
35use crate::script_runtime::CanGc;
36
37pub(crate) trait PermissionAlgorithm {
38 type Descriptor;
39 #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
40 type Status;
41 fn create_descriptor(
42 cx: &mut js::context::JSContext,
43 permission_descriptor_obj: *mut JSObject,
44 ) -> Result<Self::Descriptor, Error>;
45 fn permission_query(
46 cx: &mut js::context::JSContext,
47 promise: &Rc<Promise>,
48 descriptor: &Self::Descriptor,
49 status: &Self::Status,
50 );
51 fn permission_request(
52 cx: &mut js::context::JSContext,
53 promise: &Rc<Promise>,
54 descriptor: &Self::Descriptor,
55 status: &Self::Status,
56 );
57 fn permission_revoke(
58 cx: &mut js::context::JSContext,
59 descriptor: &Self::Descriptor,
60 status: &Self::Status,
61 );
62}
63
64enum Operation {
65 Query,
66 Request,
67 Revoke,
68}
69
70#[dom_struct]
72pub(crate) struct Permissions {
73 reflector_: Reflector,
74}
75
76impl Permissions {
77 pub(crate) fn new_inherited() -> Permissions {
78 Permissions {
79 reflector_: Reflector::new(),
80 }
81 }
82
83 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Permissions> {
84 reflect_dom_object(Box::new(Permissions::new_inherited()), global, can_gc)
85 }
86
87 #[expect(non_snake_case)]
91 fn manipulate(
92 &self,
93 cx: &mut js::context::JSContext,
94 op: Operation,
95 permissionDesc: *mut JSObject,
96 promise: Option<Rc<Promise>>,
97 ) -> Rc<Promise> {
98 let p = match promise {
100 Some(promise) => promise,
101 None => {
102 let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
103 Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), CanGc::from_cx(cx))
104 },
105 };
106
107 let root_desc = match Permissions::create_descriptor(cx, permissionDesc) {
109 Ok(descriptor) => descriptor,
110 Err(error) => {
111 p.reject_error(error, CanGc::from_cx(cx));
112 return p;
113 },
114 };
115
116 let status = PermissionStatus::new(&self.global(), &root_desc, CanGc::from_cx(cx));
118
119 match root_desc.name {
121 #[cfg(feature = "bluetooth")]
122 PermissionName::Bluetooth => {
123 let bluetooth_desc = match Bluetooth::create_descriptor(cx, permissionDesc) {
124 Ok(descriptor) => descriptor,
125 Err(error) => {
126 p.reject_error(error, CanGc::from_cx(cx));
127 return p;
128 },
129 };
130
131 let result = BluetoothPermissionResult::new(cx, &self.global(), &status);
133
134 match op {
135 Operation::Request => {
137 Bluetooth::permission_request(cx, &p, &bluetooth_desc, &result)
138 },
139
140 Operation::Query => {
142 Bluetooth::permission_query(cx, &p, &bluetooth_desc, &result)
143 },
144
145 Operation::Revoke => {
146 let globalscope = self.global();
148 globalscope
149 .permission_state_invocation_results()
150 .borrow_mut()
151 .remove(&root_desc.name);
152
153 Bluetooth::permission_revoke(cx, &bluetooth_desc, &result)
155 },
156 }
157 },
158 _ => {
159 match op {
160 Operation::Request => {
161 Permissions::permission_request(cx, &p, &root_desc, &status);
163
164 p.resolve_native(&status, CanGc::from_cx(cx));
168 },
169 Operation::Query => {
170 Permissions::permission_query(cx, &p, &root_desc, &status);
172
173 p.resolve_native(&status, CanGc::from_cx(cx));
175 },
176
177 Operation::Revoke => {
178 let globalscope = self.global();
180 globalscope
181 .permission_state_invocation_results()
182 .borrow_mut()
183 .remove(&root_desc.name);
184
185 Permissions::permission_revoke(cx, &root_desc, &status);
187 },
188 }
189 },
190 };
191 match op {
192 Operation::Revoke => self.manipulate(cx, Operation::Query, permissionDesc, Some(p)),
194
195 _ => p,
197 }
198 }
199}
200
201#[expect(non_snake_case)]
202impl PermissionsMethods<crate::DomTypeHolder> for Permissions {
203 fn Query(&self, cx: &mut js::context::JSContext, permissionDesc: *mut JSObject) -> Rc<Promise> {
205 self.manipulate(cx, Operation::Query, permissionDesc, None)
206 }
207
208 fn Request(
210 &self,
211 cx: &mut js::context::JSContext,
212 permissionDesc: *mut JSObject,
213 ) -> Rc<Promise> {
214 self.manipulate(cx, Operation::Request, permissionDesc, None)
215 }
216
217 fn Revoke(
219 &self,
220 cx: &mut js::context::JSContext,
221 permissionDesc: *mut JSObject,
222 ) -> Rc<Promise> {
223 self.manipulate(cx, Operation::Revoke, permissionDesc, None)
224 }
225}
226
227impl PermissionAlgorithm for Permissions {
228 type Descriptor = PermissionDescriptor;
229 type Status = PermissionStatus;
230
231 fn create_descriptor(
232 cx: &mut js::context::JSContext,
233 permission_descriptor_obj: *mut JSObject,
234 ) -> Result<PermissionDescriptor, Error> {
235 rooted!(&in(cx) let mut property = UndefinedValue());
236 property
237 .handle_mut()
238 .set(ObjectValue(permission_descriptor_obj));
239 match PermissionDescriptor::new(cx.into(), property.handle(), CanGc::from_cx(cx)) {
240 Ok(ConversionResult::Success(descriptor)) => Ok(descriptor),
241 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
242 Err(_) => Err(Error::JSFailed),
243 }
244 }
245
246 fn permission_query(
258 _cx: &mut js::context::JSContext,
259 _promise: &Rc<Promise>,
260 _descriptor: &PermissionDescriptor,
261 status: &PermissionStatus,
262 ) {
263 status.set_state(descriptor_permission_state(status.get_query(), None));
265 }
266
267 fn permission_request(
269 cx: &mut js::context::JSContext,
270 promise: &Rc<Promise>,
271 descriptor: &PermissionDescriptor,
272 status: &PermissionStatus,
273 ) {
274 Permissions::permission_query(cx, promise, descriptor, status);
276
277 match status.State() {
278 PermissionState::Prompt => {
280 let permission_name = status.get_query();
281 let globalscope = GlobalScope::current().expect("No current global object");
282 request_permission_to_use(permission_name, &globalscope);
283 },
284
285 _ => return,
287 }
288
289 Permissions::permission_query(cx, promise, descriptor, status);
291 }
292
293 fn permission_revoke(
294 _cx: &mut js::context::JSContext,
295 _descriptor: &PermissionDescriptor,
296 _status: &PermissionStatus,
297 ) {
298 }
299}
300
301pub(crate) fn descriptor_permission_state(
303 feature: PermissionName,
304 env_settings_obj: Option<&GlobalScope>,
305) -> PermissionState {
306 let global_scope = match env_settings_obj {
308 Some(env_settings_obj) => DomRoot::from_ref(env_settings_obj),
309 None => GlobalScope::current().expect("No current global object"),
310 };
311
312 if !global_scope.is_secure_context() {
314 if pref!(dom_permissions_testing_allowed_in_nonsecure_contexts) {
315 return PermissionState::Granted;
316 }
317 return PermissionState::Denied;
318 }
319
320 if let Some(window) = global_scope.downcast::<Window>() &&
328 !window.Document().allowed_to_use_feature(feature)
329 {
330 return PermissionState::Denied;
331 }
332
333 if let Some(entry) = global_scope
340 .permission_state_invocation_results()
341 .borrow()
342 .get(&feature)
343 {
344 return *entry;
345 }
346
347 PermissionState::Prompt
351}
352
353pub(crate) fn request_permission_to_use(
355 name: PermissionName,
356 global_scope: &GlobalScope,
357) -> PermissionState {
358 let state = descriptor_permission_state(name, Some(global_scope));
359 if state != PermissionState::Prompt {
360 return state;
361 }
362
363 let state = prompt_user_from_embedder(name, global_scope);
364 global_scope
365 .permission_state_invocation_results()
366 .borrow_mut()
367 .insert(name, state);
368 descriptor_permission_state(name, Some(global_scope))
369}
370
371fn prompt_user_from_embedder(name: PermissionName, global_scope: &GlobalScope) -> PermissionState {
372 let Some(webview_id) = global_scope.webview_id() else {
373 warn!("Requesting permissions from non-webview-associated global scope");
374 return PermissionState::Denied;
375 };
376 let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel!");
377 global_scope.send_to_embedder(EmbedderMsg::PromptPermission(
378 webview_id,
379 name.convert(),
380 sender,
381 ));
382
383 match receiver.recv() {
384 Ok(AllowOrDeny::Allow) => PermissionState::Granted,
385 Ok(AllowOrDeny::Deny) => PermissionState::Denied,
386 Err(e) => {
387 warn!(
388 "Failed to receive permission state from embedder ({:?}).",
389 e
390 );
391 PermissionState::Denied
392 },
393 }
394}
395
396impl Convert<PermissionFeature> for PermissionName {
397 fn convert(self) -> PermissionFeature {
398 match self {
399 PermissionName::Geolocation => PermissionFeature::Geolocation,
400 PermissionName::Notifications => PermissionFeature::Notifications,
401 PermissionName::Push => PermissionFeature::Push,
402 PermissionName::Midi => PermissionFeature::Midi,
403 PermissionName::Camera => PermissionFeature::Camera,
404 PermissionName::Microphone => PermissionFeature::Microphone,
405 PermissionName::Speaker => PermissionFeature::Speaker,
406 PermissionName::Device_info => PermissionFeature::DeviceInfo,
407 PermissionName::Background_sync => PermissionFeature::BackgroundSync,
408 PermissionName::Bluetooth => PermissionFeature::Bluetooth,
409 PermissionName::Persistent_storage => PermissionFeature::PersistentStorage,
410 PermissionName::Screen_wake_lock => PermissionFeature::ScreenWakeLock,
411 }
412 }
413}