1use std::rc::Rc;
6
7use base::generic_channel;
8use dom_struct::dom_struct;
9use embedder_traits::{self, AllowOrDeny, EmbedderMsg, PermissionFeature};
10use js::conversions::ConversionResult;
11use js::jsapi::JSObject;
12use js::jsval::{ObjectValue, UndefinedValue};
13use script_bindings::inheritance::Castable;
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, JSContext};
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: JSContext,
42 permission_descriptor_obj: *mut JSObject,
43 can_gc: CanGc,
44 ) -> Result<Self::Descriptor, Error>;
45 fn permission_query(
46 cx: JSContext,
47 promise: &Rc<Promise>,
48 descriptor: &Self::Descriptor,
49 status: &Self::Status,
50 );
51 fn permission_request(
52 cx: JSContext,
53 promise: &Rc<Promise>,
54 descriptor: &Self::Descriptor,
55 status: &Self::Status,
56 );
57 fn permission_revoke(descriptor: &Self::Descriptor, status: &Self::Status, can_gc: CanGc);
58}
59
60enum Operation {
61 Query,
62 Request,
63 Revoke,
64}
65
66#[dom_struct]
68pub(crate) struct Permissions {
69 reflector_: Reflector,
70}
71
72impl Permissions {
73 pub(crate) fn new_inherited() -> Permissions {
74 Permissions {
75 reflector_: Reflector::new(),
76 }
77 }
78
79 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Permissions> {
80 reflect_dom_object(Box::new(Permissions::new_inherited()), global, can_gc)
81 }
82
83 #[allow(non_snake_case)]
87 fn manipulate(
88 &self,
89 op: Operation,
90 cx: JSContext,
91 permissionDesc: *mut JSObject,
92 promise: Option<Rc<Promise>>,
93 can_gc: CanGc,
94 ) -> Rc<Promise> {
95 let p = match promise {
97 Some(promise) => promise,
98 None => {
99 let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
100 Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc)
101 },
102 };
103
104 let root_desc = match Permissions::create_descriptor(cx, permissionDesc, can_gc) {
106 Ok(descriptor) => descriptor,
107 Err(error) => {
108 p.reject_error(error, can_gc);
109 return p;
110 },
111 };
112
113 let status = PermissionStatus::new(&self.global(), &root_desc, can_gc);
115
116 match root_desc.name {
118 #[cfg(feature = "bluetooth")]
119 PermissionName::Bluetooth => {
120 let bluetooth_desc = match Bluetooth::create_descriptor(cx, permissionDesc, can_gc)
121 {
122 Ok(descriptor) => descriptor,
123 Err(error) => {
124 p.reject_error(error, can_gc);
125 return p;
126 },
127 };
128
129 let result = BluetoothPermissionResult::new(&self.global(), &status, can_gc);
131
132 match op {
133 Operation::Request => {
135 Bluetooth::permission_request(cx, &p, &bluetooth_desc, &result)
136 },
137
138 Operation::Query => {
140 Bluetooth::permission_query(cx, &p, &bluetooth_desc, &result)
141 },
142
143 Operation::Revoke => {
144 let globalscope = self.global();
146 globalscope
147 .permission_state_invocation_results()
148 .borrow_mut()
149 .remove(&root_desc.name);
150
151 Bluetooth::permission_revoke(&bluetooth_desc, &result, can_gc)
153 },
154 }
155 },
156 _ => {
157 match op {
158 Operation::Request => {
159 Permissions::permission_request(cx, &p, &root_desc, &status);
161
162 p.resolve_native(&status, can_gc);
166 },
167 Operation::Query => {
168 Permissions::permission_query(cx, &p, &root_desc, &status);
170
171 p.resolve_native(&status, can_gc);
173 },
174
175 Operation::Revoke => {
176 let globalscope = self.global();
178 globalscope
179 .permission_state_invocation_results()
180 .borrow_mut()
181 .remove(&root_desc.name);
182
183 Permissions::permission_revoke(&root_desc, &status, can_gc);
185 },
186 }
187 },
188 };
189 match op {
190 Operation::Revoke => {
192 self.manipulate(Operation::Query, cx, permissionDesc, Some(p), can_gc)
193 },
194
195 _ => p,
197 }
198 }
199}
200
201#[allow(non_snake_case)]
202impl PermissionsMethods<crate::DomTypeHolder> for Permissions {
203 fn Query(&self, cx: JSContext, permissionDesc: *mut JSObject, can_gc: CanGc) -> Rc<Promise> {
205 self.manipulate(Operation::Query, cx, permissionDesc, None, can_gc)
206 }
207
208 fn Request(&self, cx: JSContext, permissionDesc: *mut JSObject, can_gc: CanGc) -> Rc<Promise> {
210 self.manipulate(Operation::Request, cx, permissionDesc, None, can_gc)
211 }
212
213 fn Revoke(&self, cx: JSContext, permissionDesc: *mut JSObject, can_gc: CanGc) -> Rc<Promise> {
215 self.manipulate(Operation::Revoke, cx, permissionDesc, None, can_gc)
216 }
217}
218
219impl PermissionAlgorithm for Permissions {
220 type Descriptor = PermissionDescriptor;
221 type Status = PermissionStatus;
222
223 fn create_descriptor(
224 cx: JSContext,
225 permission_descriptor_obj: *mut JSObject,
226 can_gc: CanGc,
227 ) -> Result<PermissionDescriptor, Error> {
228 rooted!(in(*cx) let mut property = UndefinedValue());
229 property
230 .handle_mut()
231 .set(ObjectValue(permission_descriptor_obj));
232 match PermissionDescriptor::new(cx, property.handle(), can_gc) {
233 Ok(ConversionResult::Success(descriptor)) => Ok(descriptor),
234 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
235 Err(_) => Err(Error::JSFailed),
236 }
237 }
238
239 fn permission_query(
251 _cx: JSContext,
252 _promise: &Rc<Promise>,
253 _descriptor: &PermissionDescriptor,
254 status: &PermissionStatus,
255 ) {
256 status.set_state(descriptor_permission_state(status.get_query(), None));
258 }
259
260 fn permission_request(
262 cx: JSContext,
263 promise: &Rc<Promise>,
264 descriptor: &PermissionDescriptor,
265 status: &PermissionStatus,
266 ) {
267 Permissions::permission_query(cx, promise, descriptor, status);
269
270 match status.State() {
271 PermissionState::Prompt => {
273 let permission_name = status.get_query();
275 let globalscope = GlobalScope::current().expect("No current global object");
276 let state = prompt_user_from_embedder(permission_name, &globalscope);
277 globalscope
278 .permission_state_invocation_results()
279 .borrow_mut()
280 .insert(permission_name, state);
281 },
282
283 _ => return,
285 }
286
287 Permissions::permission_query(cx, promise, descriptor, status);
289 }
290
291 fn permission_revoke(
292 _descriptor: &PermissionDescriptor,
293 _status: &PermissionStatus,
294 _can_gc: CanGc,
295 ) {
296 }
297}
298
299pub(crate) fn descriptor_permission_state(
301 feature: PermissionName,
302 env_settings_obj: Option<&GlobalScope>,
303) -> PermissionState {
304 let global_scope = match env_settings_obj {
306 Some(env_settings_obj) => DomRoot::from_ref(env_settings_obj),
307 None => GlobalScope::current().expect("No current global object"),
308 };
309
310 if !global_scope.is_secure_context() {
312 if pref!(dom_permissions_testing_allowed_in_nonsecure_contexts) {
313 return PermissionState::Granted;
314 }
315 return PermissionState::Denied;
316 }
317
318 if let Some(window) = global_scope.downcast::<Window>() {
326 if !window.Document().allowed_to_use_feature(feature) {
327 return PermissionState::Denied;
328 }
329 }
330
331 if let Some(entry) = global_scope
338 .permission_state_invocation_results()
339 .borrow()
340 .get(&feature)
341 {
342 return *entry;
343 }
344
345 PermissionState::Prompt
349}
350
351fn prompt_user_from_embedder(name: PermissionName, global_scope: &GlobalScope) -> PermissionState {
352 let Some(webview_id) = global_scope.webview_id() else {
353 warn!("Requesting permissions from non-webview-associated global scope");
354 return PermissionState::Denied;
355 };
356 let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel!");
357 global_scope.send_to_embedder(EmbedderMsg::PromptPermission(
358 webview_id,
359 name.convert(),
360 sender,
361 ));
362
363 match receiver.recv() {
364 Ok(AllowOrDeny::Allow) => PermissionState::Granted,
365 Ok(AllowOrDeny::Deny) => PermissionState::Denied,
366 Err(e) => {
367 warn!(
368 "Failed to receive permission state from embedder ({:?}).",
369 e
370 );
371 PermissionState::Denied
372 },
373 }
374}
375
376impl Convert<PermissionFeature> for PermissionName {
377 fn convert(self) -> PermissionFeature {
378 match self {
379 PermissionName::Geolocation => PermissionFeature::Geolocation,
380 PermissionName::Notifications => PermissionFeature::Notifications,
381 PermissionName::Push => PermissionFeature::Push,
382 PermissionName::Midi => PermissionFeature::Midi,
383 PermissionName::Camera => PermissionFeature::Camera,
384 PermissionName::Microphone => PermissionFeature::Microphone,
385 PermissionName::Speaker => PermissionFeature::Speaker,
386 PermissionName::Device_info => PermissionFeature::DeviceInfo,
387 PermissionName::Background_sync => PermissionFeature::BackgroundSync,
388 PermissionName::Bluetooth => PermissionFeature::Bluetooth,
389 PermissionName::Persistent_storage => PermissionFeature::PersistentStorage,
390 }
391 }
392}