Skip to main content

script/dom/geolocation/
geolocation.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/. */
4use std::cell::{Cell, RefCell};
5use std::rc::Rc;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::gc::HandleValue;
10use js::jsapi::IsCallable;
11use rustc_hash::FxHashSet;
12use script_bindings::callback::{ExceptionHandling, OwnerWindow};
13use script_bindings::codegen::GenericBindings::GeolocationBinding::Geolocation_Binding::GeolocationMethods;
14use script_bindings::codegen::GenericBindings::GeolocationBinding::{
15    PositionCallback, PositionErrorCallback, PositionOptions,
16};
17use script_bindings::codegen::GenericBindings::PermissionStatusBinding::PermissionName;
18use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
19use script_bindings::domstring::DOMString;
20use script_bindings::error::{Error, Fallible};
21use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
22use script_bindings::root::DomRoot;
23
24use crate::dom::bindings::codegen::DomTypeHolder::DomTypeHolder;
25use crate::dom::bindings::reflector::DomGlobal;
26use crate::dom::geolocationpositionerror::GeolocationPositionError;
27use crate::dom::globalscope::GlobalScope;
28
29fn cast_error_callback(
30    cx: &mut JSContext,
31    error_callback: HandleValue,
32) -> Fallible<Option<Rc<PositionErrorCallback<DomTypeHolder>>>> {
33    if error_callback.get().is_object() {
34        let error_callback = error_callback.to_object();
35        #[expect(unsafe_code)]
36        unsafe {
37            if IsCallable(error_callback) {
38                Ok(Some(PositionErrorCallback::new(cx, error_callback)))
39            } else {
40                Err(Error::Type(c"Value is not callable.".to_owned()))
41            }
42        }
43    } else if error_callback.get().is_null_or_undefined() {
44        Ok(None)
45    } else {
46        Err(Error::Type(c"Value is not an object.".to_owned()))
47    }
48}
49
50#[dom_struct]
51pub struct Geolocation {
52    reflector_: Reflector,
53    /// <https://www.w3.org/TR/geolocation/#dfn-watchids>
54    watch_ids: RefCell<FxHashSet<u32>>,
55    next_watch_id: Cell<u32>,
56}
57
58impl Geolocation {
59    fn new_inherited() -> Self {
60        Geolocation {
61            reflector_: Reflector::new(),
62            watch_ids: RefCell::new(FxHashSet::default()),
63            next_watch_id: Cell::new(1),
64        }
65    }
66
67    pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Self> {
68        reflect_dom_object_with_cx(Box::new(Self::new_inherited()), global, cx)
69    }
70
71    /// <https://www.w3.org/TR/geolocation/#dfn-request-a-position>
72    fn request_position(
73        &self,
74        cx: &mut JSContext,
75        _success_callback: Rc<PositionCallback<DomTypeHolder>>,
76        error_callback: Option<Rc<PositionErrorCallback<DomTypeHolder>>>,
77        _options: &PositionOptions,
78        watch_id: Option<u32>,
79    ) -> Fallible<()> {
80        // Step 1. Let watchIDs be geolocation's [[watchIDs]].
81        // Step 2. Let document be the geolocation's relevant global object's associated Document.
82        let document = self.global().as_window().Document();
83        // Step 3. If document is not allowed to use the "geolocation" feature:
84        if !document.allowed_to_use_feature(PermissionName::Geolocation) {
85            if let Some(id) = watch_id {
86                // Step 3.1 If watchId was passed, remove watchId from watchIDs.
87                self.watch_ids.borrow_mut().remove(&id);
88            }
89            // Step 3.2. Call back with error passing errorCallback and PERMISSION_DENIED.
90            if let Some(error_callback) = error_callback {
91                let position_error = GeolocationPositionError::permission_denied(
92                    cx,
93                    &self.global(),
94                    DOMString::from("User denied Geolocation".to_string()),
95                );
96                error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
97            }
98            // Step 3.3 Terminate this algorithm.
99            return Ok(());
100        }
101        // Step 4. If geolocation's environment settings object is a non-secure context:
102        if !self.global().is_secure_context() {
103            if let Some(id) = watch_id {
104                // Step 4.1 If watchId was passed, remove watchId from watchIDs.
105                self.watch_ids.borrow_mut().remove(&id);
106            }
107            // Step 4.2. Call back with error passing errorCallback and PERMISSION_DENIED.
108            if let Some(error_callback) = error_callback {
109                let position_error = GeolocationPositionError::permission_denied(
110                    cx,
111                    &self.global(),
112                    DOMString::from("Insecure context for Geolocation".to_string()),
113                );
114                error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
115            }
116            // Step 4.3 Terminate this algorithm.
117            return Ok(());
118        }
119        // TODO: Step 5
120        // TODO: Step 6. Let descriptor be a new PermissionDescriptor whose name is "geolocation".
121
122        Ok(())
123    }
124}
125
126impl GeolocationMethods<DomTypeHolder> for Geolocation {
127    /// <https://www.w3.org/TR/geolocation/#dom-geolocation-getcurrentposition>
128    fn GetCurrentPosition(
129        &self,
130        cx: &mut JSContext,
131        success_callback: Rc<PositionCallback<DomTypeHolder>>,
132        error_callback: HandleValue,
133        options: &PositionOptions,
134    ) -> Fallible<()> {
135        let error_callback = cast_error_callback(cx, error_callback)?;
136        // Step 1. If this's relevant global object's associated Document is not fully active:
137        if !self.global().as_window().Document().is_active() {
138            // Step 1.1 Call back with error errorCallback and POSITION_UNAVAILABLE.
139            if let Some(error_callback) = error_callback {
140                let position_error = GeolocationPositionError::position_unavailable(
141                    cx,
142                    &self.global(),
143                    DOMString::from("Document is not fully active".to_string()),
144                );
145                error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
146            }
147            // Step 1.2 Terminate this algorithm.
148            return Ok(());
149        }
150        // Step 2. Request a position passing this, successCallback, errorCallback, and options.
151        self.request_position(cx, success_callback, error_callback, options, None)
152    }
153
154    /// <https://www.w3.org/TR/geolocation/#watchposition-method>
155    fn WatchPosition(
156        &self,
157        cx: &mut JSContext,
158        success_callback: Rc<PositionCallback<DomTypeHolder>>,
159        error_callback: HandleValue,
160        options: &PositionOptions,
161    ) -> Fallible<i32> {
162        let error_callback = cast_error_callback(cx, error_callback)?;
163        // Step 1. If this's relevant global object's associated Document is not fully active:
164        if !self.global().as_window().Document().is_active() {
165            // Step 1.1 Call back with error errorCallback and POSITION_UNAVAILABLE.
166            if let Some(error_callback) = error_callback {
167                let position_error = GeolocationPositionError::position_unavailable(
168                    cx,
169                    &self.global(),
170                    DOMString::from("Document is not fully active".to_string()),
171                );
172                error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
173            }
174            // Step 1.2 Return 0.
175            return Ok(0);
176        }
177        // Step 2. Let watchId be an implementation-defined unsigned long that is greater than zero.
178        let watch_id = self.next_watch_id.get();
179        self.next_watch_id.set(watch_id + 1);
180        // Step 3. Append watchId to this's [[watchIDs]].
181        self.watch_ids.borrow_mut().insert(watch_id);
182        // Step 4. Request a position passing this, successCallback, errorCallback, options, and watchId.
183        self.request_position(
184            cx,
185            success_callback,
186            error_callback,
187            options,
188            Some(watch_id),
189        )?;
190        // Step 5. Return watchId.
191        Ok(watch_id as i32)
192    }
193
194    /// <https://www.w3.org/TR/geolocation/#clearwatch-method>
195    fn ClearWatch(&self, watch_id: i32) {
196        let watch_id = u32::try_from(watch_id).ok();
197        if let Some(id) = watch_id {
198            self.watch_ids.borrow_mut().remove(&id);
199        }
200    }
201}
202
203impl OwnerWindow<crate::DomTypeHolder> for Geolocation {}