1use std::rc::Rc;
6
7use dom_struct::dom_struct;
8use embedder_traits::{self, AllowOrDeny, EmbedderMsg, PermissionFeature, WakeLockType};
9use js::context::JSContext;
10use js::conversions::ConversionResult;
11use js::jsapi::JSObject;
12use js::jsval::{ObjectValue, UndefinedValue};
13use js::realm::CurrentRealm;
14use script_bindings::inheritance::Castable;
15use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
16use servo_base::generic_channel;
17use servo_config::pref;
18
19use crate::conversions::Convert;
20use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{
21 PermissionDescriptor, PermissionName, PermissionState, PermissionStatusMethods,
22};
23use crate::dom::bindings::codegen::Bindings::PermissionsBinding::PermissionsMethods;
24use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
25use crate::dom::bindings::error::Error;
26use crate::dom::bindings::reflector::DomGlobal;
27use crate::dom::bindings::root::DomRoot;
28#[cfg(feature = "bluetooth")]
29use crate::dom::bluetooth::Bluetooth;
30#[cfg(feature = "bluetooth")]
31use crate::dom::bluetoothpermissionresult::BluetoothPermissionResult;
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::permissionstatus::PermissionStatus;
34use crate::dom::promise::Promise;
35use crate::dom::window::Window;
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 JSContext,
43 permission_descriptor_obj: js::gc::HandleValue,
44 ) -> Result<Self::Descriptor, Error>;
45 fn permission_query(
46 cx: &mut JSContext,
47 promise: &Rc<Promise>,
48 descriptor: &Self::Descriptor,
49 status: &Self::Status,
50 );
51 fn permission_request(
52 cx: &mut JSContext,
53 promise: &Rc<Promise>,
54 descriptor: &Self::Descriptor,
55 status: &Self::Status,
56 );
57 fn permission_revoke(cx: &mut JSContext, descriptor: &Self::Descriptor, status: &Self::Status);
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(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Permissions> {
80 reflect_dom_object_with_cx(Box::new(Permissions::new_inherited()), global, cx)
81 }
82
83 fn manipulate(
87 &self,
88 cx: &mut CurrentRealm,
89 op: Operation,
90 permission_desc: *mut JSObject,
91 promise: Option<Rc<Promise>>,
92 ) -> Rc<Promise> {
93 rooted!(&in(cx) let mut permission_desc_value = UndefinedValue());
94 permission_desc_value
95 .handle_mut()
96 .set(ObjectValue(permission_desc));
97
98 let p = match promise {
100 Some(promise) => promise,
101 None => Promise::new_in_realm(cx),
102 };
103
104 let root_desc = match Permissions::create_descriptor(cx, permission_desc_value.handle()) {
106 Ok(descriptor) => descriptor,
107 Err(error) => {
108 p.reject_error(cx, error);
109 return p;
110 },
111 };
112
113 let status = PermissionStatus::new(cx, &self.global(), &root_desc);
115
116 match root_desc.name {
118 #[cfg(feature = "bluetooth")]
119 PermissionName::Bluetooth => {
120 let bluetooth_desc =
121 match Bluetooth::create_descriptor(cx, permission_desc_value.handle()) {
122 Ok(descriptor) => descriptor,
123 Err(error) => {
124 p.reject_error(cx, error);
125 return p;
126 },
127 };
128
129 let result = BluetoothPermissionResult::new(cx, &self.global(), &status);
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(cx, &bluetooth_desc, &result)
153 },
154 }
155 },
156 _ => {
157 match op {
158 Operation::Request => {
159 Permissions::permission_request(cx, &p, &root_desc, &status);
161
162 p.resolve_native(cx, &status);
166 },
167 Operation::Query => {
168 Permissions::permission_query(cx, &p, &root_desc, &status);
170
171 p.resolve_native(cx, &status);
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(cx, &root_desc, &status);
185 },
186 }
187 },
188 };
189 match op {
190 Operation::Revoke => self.manipulate(cx, Operation::Query, permission_desc, Some(p)),
192
193 _ => p,
195 }
196 }
197}
198
199impl PermissionsMethods<crate::DomTypeHolder> for Permissions {
201 fn Query(&self, cx: &mut CurrentRealm, permission_desc: *mut JSObject) -> Rc<Promise> {
203 self.manipulate(cx, Operation::Query, permission_desc, None)
204 }
205
206 fn Request(&self, cx: &mut CurrentRealm, permission_desc: *mut JSObject) -> Rc<Promise> {
208 self.manipulate(cx, Operation::Request, permission_desc, None)
209 }
210
211 fn Revoke(&self, cx: &mut CurrentRealm, permission_desc: *mut JSObject) -> Rc<Promise> {
213 self.manipulate(cx, Operation::Revoke, permission_desc, None)
214 }
215}
216
217impl PermissionAlgorithm for Permissions {
218 type Descriptor = PermissionDescriptor;
219 type Status = PermissionStatus;
220
221 fn create_descriptor(
222 cx: &mut JSContext,
223 property: js::gc::HandleValue,
224 ) -> Result<PermissionDescriptor, Error> {
225 match PermissionDescriptor::new(cx, property) {
226 Ok(ConversionResult::Success(descriptor)) => Ok(descriptor),
227 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
228 Err(_) => Err(Error::JSFailed),
229 }
230 }
231
232 fn permission_query(
244 _cx: &mut JSContext,
245 _promise: &Rc<Promise>,
246 _descriptor: &PermissionDescriptor,
247 status: &PermissionStatus,
248 ) {
249 status.set_state(descriptor_permission_state(status.get_query(), None));
251 }
252
253 fn permission_request(
255 cx: &mut JSContext,
256 promise: &Rc<Promise>,
257 descriptor: &PermissionDescriptor,
258 status: &PermissionStatus,
259 ) {
260 Permissions::permission_query(cx, promise, descriptor, status);
262
263 match status.State() {
264 PermissionState::Prompt => {
266 let permission_name = status.get_query();
267 let globalscope = GlobalScope::current().expect("No current global object");
268 request_permission_to_use(permission_name, &globalscope);
269 },
270
271 _ => return,
273 }
274
275 Permissions::permission_query(cx, promise, descriptor, status);
277 }
278
279 fn permission_revoke(
280 _cx: &mut JSContext,
281 _descriptor: &PermissionDescriptor,
282 _status: &PermissionStatus,
283 ) {
284 }
285}
286
287pub(crate) fn descriptor_permission_state(
289 feature: PermissionName,
290 env_settings_obj: Option<&GlobalScope>,
291) -> PermissionState {
292 let global_scope = match env_settings_obj {
294 Some(env_settings_obj) => DomRoot::from_ref(env_settings_obj),
295 None => GlobalScope::current().expect("No current global object"),
296 };
297
298 if !global_scope.is_secure_context() {
300 if pref!(dom_permissions_testing_allowed_in_nonsecure_contexts) {
301 return PermissionState::Granted;
302 }
303 return PermissionState::Denied;
304 }
305
306 if let Some(window) = global_scope.downcast::<Window>() &&
314 !window.Document().allowed_to_use_feature(feature)
315 {
316 return PermissionState::Denied;
317 }
318
319 if let Some(entry) = global_scope
326 .permission_state_invocation_results()
327 .borrow()
328 .get(&feature)
329 {
330 return *entry;
331 }
332
333 PermissionState::Prompt
337}
338
339pub(crate) fn request_permission_to_use(
341 name: PermissionName,
342 global_scope: &GlobalScope,
343) -> PermissionState {
344 let state = descriptor_permission_state(name, Some(global_scope));
345 if state != PermissionState::Prompt {
346 return state;
347 }
348
349 let state = prompt_user_from_embedder(name, global_scope);
350 global_scope
351 .permission_state_invocation_results()
352 .borrow_mut()
353 .insert(name, state);
354 descriptor_permission_state(name, Some(global_scope))
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 PermissionName::Screen_wake_lock => {
397 PermissionFeature::ScreenWakeLock(WakeLockType::Screen)
398 },
399 PermissionName::Gamepad => PermissionFeature::Gamepad,
400 }
401 }
402}