1use dom_struct::dom_struct;
6use embedder_traits::{self, AllowOrDeny, EmbedderMsg, PermissionFeature, WakeLockType};
7use js::context::JSContext;
8use js::conversions::ConversionResult;
9use js::jsapi::JSObject;
10use js::jsval::{ObjectValue, UndefinedValue};
11use js::realm::CurrentRealm;
12use script_bindings::inheritance::Castable;
13use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
14use servo_base::generic_channel;
15use servo_config::pref;
16
17use crate::conversions::Convert;
18use crate::dom::RootedPromise;
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::dom::window::Window;
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 JSContext,
42 permission_descriptor_obj: js::gc::HandleValue,
43 ) -> Result<Self::Descriptor, Error>;
44 fn permission_query(
45 cx: &mut JSContext,
46 promise: &RootedPromise,
47 descriptor: &Self::Descriptor,
48 status: &Self::Status,
49 );
50 fn permission_request(
51 cx: &mut JSContext,
52 promise: &RootedPromise,
53 descriptor: &Self::Descriptor,
54 status: &Self::Status,
55 );
56 fn permission_revoke(cx: &mut JSContext, descriptor: &Self::Descriptor, status: &Self::Status);
57}
58
59enum Operation {
60 Query,
61 Request,
62 Revoke,
63}
64
65#[dom_struct]
67pub(crate) struct Permissions {
68 reflector_: Reflector,
69}
70
71impl Permissions {
72 pub(crate) fn new_inherited() -> Permissions {
73 Permissions {
74 reflector_: Reflector::new(),
75 }
76 }
77
78 pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Permissions> {
79 reflect_dom_object_with_cx(Box::new(Permissions::new_inherited()), global, cx)
80 }
81
82 fn manipulate(
86 &self,
87 cx: &mut CurrentRealm,
88 op: Operation,
89 permission_desc: *mut JSObject,
90 promise: Option<RootedPromise>,
91 ) -> RootedPromise {
92 rooted!(&in(cx) let mut permission_desc_value = UndefinedValue());
93 permission_desc_value
94 .handle_mut()
95 .set(ObjectValue(permission_desc));
96
97 let p = match promise {
99 Some(promise) => promise,
100 None => Promise::new_in_realm_rooted(cx),
101 };
102
103 let root_desc = match Permissions::create_descriptor(cx, permission_desc_value.handle()) {
105 Ok(descriptor) => descriptor,
106 Err(error) => {
107 p.reject_error(cx, error);
108 return p;
109 },
110 };
111
112 let status = PermissionStatus::new(cx, &self.global(), &root_desc);
114
115 match root_desc.name {
117 #[cfg(feature = "bluetooth")]
118 PermissionName::Bluetooth => {
119 let bluetooth_desc =
120 match Bluetooth::create_descriptor(cx, permission_desc_value.handle()) {
121 Ok(descriptor) => descriptor,
122 Err(error) => {
123 p.reject_error(cx, error);
124 return p;
125 },
126 };
127
128 let result = BluetoothPermissionResult::new(cx, &self.global(), &status);
130
131 match op {
132 Operation::Request => {
134 Bluetooth::permission_request(cx, &p, &bluetooth_desc, &result)
135 },
136
137 Operation::Query => {
139 Bluetooth::permission_query(cx, &p, &bluetooth_desc, &result)
140 },
141
142 Operation::Revoke => {
143 let globalscope = self.global();
145 globalscope
146 .permission_state_invocation_results()
147 .borrow_mut()
148 .remove(&root_desc.name);
149
150 Bluetooth::permission_revoke(cx, &bluetooth_desc, &result)
152 },
153 }
154 },
155 _ => {
156 match op {
157 Operation::Request => {
158 Permissions::permission_request(cx, &p, &root_desc, &status);
160
161 p.resolve_native(cx, &status);
165 },
166 Operation::Query => {
167 Permissions::permission_query(cx, &p, &root_desc, &status);
169
170 p.resolve_native(cx, &status);
172 },
173
174 Operation::Revoke => {
175 let globalscope = self.global();
177 globalscope
178 .permission_state_invocation_results()
179 .borrow_mut()
180 .remove(&root_desc.name);
181
182 Permissions::permission_revoke(cx, &root_desc, &status);
184 },
185 }
186 },
187 };
188 match op {
189 Operation::Revoke => self.manipulate(cx, Operation::Query, permission_desc, Some(p)),
191
192 _ => p,
194 }
195 }
196}
197
198impl PermissionsMethods<crate::DomTypeHolder> for Permissions {
200 fn Query(&self, cx: &mut CurrentRealm, permission_desc: *mut JSObject) -> RootedPromise {
202 self.manipulate(cx, Operation::Query, permission_desc, None)
203 }
204
205 fn Request(&self, cx: &mut CurrentRealm, permission_desc: *mut JSObject) -> RootedPromise {
207 self.manipulate(cx, Operation::Request, permission_desc, None)
208 }
209
210 fn Revoke(&self, cx: &mut CurrentRealm, permission_desc: *mut JSObject) -> RootedPromise {
212 self.manipulate(cx, Operation::Revoke, permission_desc, None)
213 }
214}
215
216impl PermissionAlgorithm for Permissions {
217 type Descriptor = PermissionDescriptor;
218 type Status = PermissionStatus;
219
220 fn create_descriptor(
221 cx: &mut JSContext,
222 property: js::gc::HandleValue,
223 ) -> Result<PermissionDescriptor, Error> {
224 match PermissionDescriptor::new(cx, property) {
225 Ok(ConversionResult::Success(descriptor)) => Ok(descriptor),
226 Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.into_owned())),
227 Err(_) => Err(Error::JSFailed),
228 }
229 }
230
231 fn permission_query(
243 _cx: &mut JSContext,
244 _promise: &RootedPromise,
245 _descriptor: &PermissionDescriptor,
246 status: &PermissionStatus,
247 ) {
248 status.set_state(descriptor_permission_state(status.get_query(), None));
250 }
251
252 fn permission_request(
254 cx: &mut JSContext,
255 promise: &RootedPromise,
256 descriptor: &PermissionDescriptor,
257 status: &PermissionStatus,
258 ) {
259 Permissions::permission_query(cx, promise, descriptor, status);
261
262 match status.State() {
263 PermissionState::Prompt => {
265 let permission_name = status.get_query();
266 let globalscope = GlobalScope::current().expect("No current global object");
267 request_permission_to_use(permission_name, &globalscope);
268 },
269
270 _ => return,
272 }
273
274 Permissions::permission_query(cx, promise, descriptor, status);
276 }
277
278 fn permission_revoke(
279 _cx: &mut JSContext,
280 _descriptor: &PermissionDescriptor,
281 _status: &PermissionStatus,
282 ) {
283 }
284}
285
286pub(crate) fn descriptor_permission_state(
288 feature: PermissionName,
289 env_settings_obj: Option<&GlobalScope>,
290) -> PermissionState {
291 let global_scope = match env_settings_obj {
293 Some(env_settings_obj) => DomRoot::from_ref(env_settings_obj),
294 None => GlobalScope::current().expect("No current global object"),
295 };
296
297 if !global_scope.is_secure_context() {
299 if pref!(dom_permissions_testing_allowed_in_nonsecure_contexts) {
300 return PermissionState::Granted;
301 }
302 return PermissionState::Denied;
303 }
304
305 if let Some(window) = global_scope.downcast::<Window>() &&
313 !window.Document().allowed_to_use_feature(feature)
314 {
315 return PermissionState::Denied;
316 }
317
318 if let Some(entry) = global_scope
325 .permission_state_invocation_results()
326 .borrow()
327 .get(&feature)
328 {
329 return *entry;
330 }
331
332 PermissionState::Prompt
336}
337
338pub(crate) fn request_permission_to_use(
340 name: PermissionName,
341 global_scope: &GlobalScope,
342) -> PermissionState {
343 let state = descriptor_permission_state(name, Some(global_scope));
344 if state != PermissionState::Prompt {
345 return state;
346 }
347
348 let state = prompt_user_from_embedder(name, global_scope);
349 global_scope
350 .permission_state_invocation_results()
351 .borrow_mut()
352 .insert(name, state);
353 descriptor_permission_state(name, Some(global_scope))
354}
355
356fn prompt_user_from_embedder(name: PermissionName, global_scope: &GlobalScope) -> PermissionState {
357 let Some(webview_id) = global_scope.webview_id() else {
358 warn!("Requesting permissions from non-webview-associated global scope");
359 return PermissionState::Denied;
360 };
361 let (sender, receiver) = generic_channel::channel().expect("Failed to create IPC channel!");
362 global_scope.send_to_embedder(EmbedderMsg::PromptPermission(
363 webview_id,
364 name.convert(),
365 sender,
366 ));
367
368 match receiver.recv() {
369 Ok(AllowOrDeny::Allow) => PermissionState::Granted,
370 Ok(AllowOrDeny::Deny) => PermissionState::Denied,
371 Err(e) => {
372 warn!(
373 "Failed to receive permission state from embedder ({:?}).",
374 e
375 );
376 PermissionState::Denied
377 },
378 }
379}
380
381impl Convert<PermissionFeature> for PermissionName {
382 fn convert(self) -> PermissionFeature {
383 match self {
384 PermissionName::Geolocation => PermissionFeature::Geolocation,
385 PermissionName::Notifications => PermissionFeature::Notifications,
386 PermissionName::Push => PermissionFeature::Push,
387 PermissionName::Midi => PermissionFeature::Midi,
388 PermissionName::Camera => PermissionFeature::Camera,
389 PermissionName::Microphone => PermissionFeature::Microphone,
390 PermissionName::Speaker => PermissionFeature::Speaker,
391 PermissionName::Device_info => PermissionFeature::DeviceInfo,
392 PermissionName::Background_sync => PermissionFeature::BackgroundSync,
393 PermissionName::Bluetooth => PermissionFeature::Bluetooth,
394 PermissionName::Persistent_storage => PermissionFeature::PersistentStorage,
395 PermissionName::Screen_wake_lock => {
396 PermissionFeature::ScreenWakeLock(WakeLockType::Screen)
397 },
398 PermissionName::Gamepad => PermissionFeature::Gamepad,
399 }
400 }
401}