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