Skip to main content

script/dom/permission/
permissions.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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// https://w3c.github.io/permissions/#permissions
66#[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    // https://w3c.github.io/permissions/#dom-permissions-query
83    // https://w3c.github.io/permissions/#dom-permissions-request
84    // https://w3c.github.io/permissions/#dom-permissions-revoke
85    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        // (Query, Request) Step 3.
98        let p = match promise {
99            Some(promise) => promise,
100            None => Promise::new_in_realm_rooted(cx),
101        };
102
103        // (Query, Request, Revoke) Step 1.
104        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        // (Query, Request) Step 5.
113        let status = PermissionStatus::new(cx, &self.global(), &root_desc);
114
115        // (Query, Request, Revoke) Step 2.
116        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                // (Query, Request) Step 5.
129                let result = BluetoothPermissionResult::new(cx, &self.global(), &status);
130
131                match op {
132                    // (Request) Step 6 - 8.
133                    Operation::Request => {
134                        Bluetooth::permission_request(cx, &p, &bluetooth_desc, &result)
135                    },
136
137                    // (Query) Step 6 - 7.
138                    Operation::Query => {
139                        Bluetooth::permission_query(cx, &p, &bluetooth_desc, &result)
140                    },
141
142                    Operation::Revoke => {
143                        // (Revoke) Step 3.
144                        let globalscope = self.global();
145                        globalscope
146                            .permission_state_invocation_results()
147                            .borrow_mut()
148                            .remove(&root_desc.name);
149
150                        // (Revoke) Step 4.
151                        Bluetooth::permission_revoke(cx, &bluetooth_desc, &result)
152                    },
153                }
154            },
155            _ => {
156                match op {
157                    Operation::Request => {
158                        // (Request) Step 6.
159                        Permissions::permission_request(cx, &p, &root_desc, &status);
160
161                        // (Request) Step 7. The default algorithm always resolve
162
163                        // (Request) Step 8.
164                        p.resolve_native(cx, &status);
165                    },
166                    Operation::Query => {
167                        // (Query) Step 6.
168                        Permissions::permission_query(cx, &p, &root_desc, &status);
169
170                        // (Query) Step 7.
171                        p.resolve_native(cx, &status);
172                    },
173
174                    Operation::Revoke => {
175                        // (Revoke) Step 3.
176                        let globalscope = self.global();
177                        globalscope
178                            .permission_state_invocation_results()
179                            .borrow_mut()
180                            .remove(&root_desc.name);
181
182                        // (Revoke) Step 4.
183                        Permissions::permission_revoke(cx, &root_desc, &status);
184                    },
185                }
186            },
187        };
188        match op {
189            // (Revoke) Step 5.
190            Operation::Revoke => self.manipulate(cx, Operation::Query, permission_desc, Some(p)),
191
192            // (Query, Request) Step 4.
193            _ => p,
194        }
195    }
196}
197
198// Currently these methods use Raw *mut JSObject which is potentially dangerous. We root this object immediately in `self.manipulate`.
199impl PermissionsMethods<crate::DomTypeHolder> for Permissions {
200    /// <https://w3c.github.io/permissions/#dom-permissions-query>
201    fn Query(&self, cx: &mut CurrentRealm, permission_desc: *mut JSObject) -> RootedPromise {
202        self.manipulate(cx, Operation::Query, permission_desc, None)
203    }
204
205    /// <https://w3c.github.io/permissions/#dom-permissions-request>
206    fn Request(&self, cx: &mut CurrentRealm, permission_desc: *mut JSObject) -> RootedPromise {
207        self.manipulate(cx, Operation::Request, permission_desc, None)
208    }
209
210    /// <https://w3c.github.io/permissions/#dom-permissions-revoke>
211    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    /// <https://w3c.github.io/permissions/#dfn-permission-query-algorithm>
232    ///
233    /// > permission query algorithm:
234    /// > Takes an instance of the permission descriptor type and a new or existing instance of
235    /// > the permission result type, and updates the permission result type instance with the
236    /// > query result. Used by Permissions' query(permission_desc) method and the
237    /// > PermissionStatus update steps. If unspecified, this defaults to the default permission
238    /// > query algorithm.
239    ///
240    /// > The default permission query algorithm, given a PermissionDescriptor
241    /// > permission_desc and a PermissionStatus status, runs the following steps:
242    fn permission_query(
243        _cx: &mut JSContext,
244        _promise: &RootedPromise,
245        _descriptor: &PermissionDescriptor,
246        status: &PermissionStatus,
247    ) {
248        // Step 1. Set status's state to permission_desc's permission state.
249        status.set_state(descriptor_permission_state(status.get_query(), None));
250    }
251
252    /// <https://w3c.github.io/permissions/#boolean-permission-request-algorithm>
253    fn permission_request(
254        cx: &mut JSContext,
255        promise: &RootedPromise,
256        descriptor: &PermissionDescriptor,
257        status: &PermissionStatus,
258    ) {
259        // Step 1.
260        Permissions::permission_query(cx, promise, descriptor, status);
261
262        match status.State() {
263            // Step 3.
264            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            // Step 2.
271            _ => return,
272        }
273
274        // Step 4.
275        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
286/// <https://w3c.github.io/permissions/#dfn-permission-state>
287pub(crate) fn descriptor_permission_state(
288    feature: PermissionName,
289    env_settings_obj: Option<&GlobalScope>,
290) -> PermissionState {
291    // Step 1. If settings wasn't passed, set it to the current settings object.
292    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    // Step 2. If settings is a non-secure context, return "denied".
298    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    // Step 3. Let feature be descriptor's name.
306    // The caller has already converted the descriptor into a name.
307
308    // Step 4. If there exists a policy-controlled feature for feature and settings'
309    // relevant global object has an associated Document run the following step:
310    //   1. Let document be settings' relevant global object's associated Document.
311    //   2. If document is not allowed to use feature, return "denied".
312    if let Some(window) = global_scope.downcast::<Window>() &&
313        !window.Document().allowed_to_use_feature(feature)
314    {
315        return PermissionState::Denied;
316    }
317
318    // Step 5. Let key be the result of generating a permission key for descriptor with settings.
319    // Step 6. Let entry be the result of getting a permission store entry with descriptor and key.
320    // Step 7. If entry is not null, return a PermissionState enum value from entry's state.
321    //
322    // TODO: We aren't making a key based on the descriptor, but on the descriptor's name. This really
323    // only matters for WebBluetooth, which adds more fields to the descriptor beyond the name.
324    if let Some(entry) = global_scope
325        .permission_state_invocation_results()
326        .borrow()
327        .get(&feature)
328    {
329        return *entry;
330    }
331
332    // Step 8. Return the PermissionState enum value that represents the permission state
333    // of feature, taking into account any permission state constraints for descriptor's
334    // name.
335    PermissionState::Prompt
336}
337
338/// <https://w3c.github.io/permissions/#request-permission-to-use>
339pub(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}