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;
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();
281 let globalscope = GlobalScope::current().expect("No current global object");
282 let state = prompt_user_from_embedder(permission_name, &globalscope);
283 globalscope
284 .permission_state_invocation_results()
285 .borrow_mut()
286 .insert(permission_name, state);
287 },
288
289 _ => return,
291 }
292
293 Permissions::permission_query(cx, promise, descriptor, status);
295 }
296
297 fn permission_revoke(
298 _cx: &mut js::context::JSContext,
299 _descriptor: &PermissionDescriptor,
300 _status: &PermissionStatus,
301 ) {
302 }
303}
304
305pub(crate) fn descriptor_permission_state(
307 feature: PermissionName,
308 env_settings_obj: Option<&GlobalScope>,
309) -> PermissionState {
310 let global_scope = match env_settings_obj {
312 Some(env_settings_obj) => DomRoot::from_ref(env_settings_obj),
313 None => GlobalScope::current().expect("No current global object"),
314 };
315
316 if !global_scope.is_secure_context() {
318 if pref!(dom_permissions_testing_allowed_in_nonsecure_contexts) {
319 return PermissionState::Granted;
320 }
321 return PermissionState::Denied;
322 }
323
324 if let Some(window) = global_scope.downcast::<Window>() {
332 if !window.Document().allowed_to_use_feature(feature) {
333 return PermissionState::Denied;
334 }
335 }
336
337 if let Some(entry) = global_scope
344 .permission_state_invocation_results()
345 .borrow()
346 .get(&feature)
347 {
348 return *entry;
349 }
350
351 PermissionState::Prompt
355}
356
357fn prompt_user_from_embedder(name: PermissionName, global_scope: &GlobalScope) -> PermissionState {
358 let Some(webview_id) = global_scope.webview_id() else {
359 warn!("Requesting permissions from non-webview-associated global scope");
360 return PermissionState::Denied;
361 };
362 let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel!");
363 global_scope.send_to_embedder(EmbedderMsg::PromptPermission(
364 webview_id,
365 name.convert(),
366 sender,
367 ));
368
369 match receiver.recv() {
370 Ok(AllowOrDeny::Allow) => PermissionState::Granted,
371 Ok(AllowOrDeny::Deny) => PermissionState::Denied,
372 Err(e) => {
373 warn!(
374 "Failed to receive permission state from embedder ({:?}).",
375 e
376 );
377 PermissionState::Denied
378 },
379 }
380}
381
382impl Convert<PermissionFeature> for PermissionName {
383 fn convert(self) -> PermissionFeature {
384 match self {
385 PermissionName::Geolocation => PermissionFeature::Geolocation,
386 PermissionName::Notifications => PermissionFeature::Notifications,
387 PermissionName::Push => PermissionFeature::Push,
388 PermissionName::Midi => PermissionFeature::Midi,
389 PermissionName::Camera => PermissionFeature::Camera,
390 PermissionName::Microphone => PermissionFeature::Microphone,
391 PermissionName::Speaker => PermissionFeature::Speaker,
392 PermissionName::Device_info => PermissionFeature::DeviceInfo,
393 PermissionName::Background_sync => PermissionFeature::BackgroundSync,
394 PermissionName::Bluetooth => PermissionFeature::Bluetooth,
395 PermissionName::Persistent_storage => PermissionFeature::PersistentStorage,
396 }
397 }
398}