script/dom/geolocation/
geolocation.rs1use std::cell::{Cell, RefCell};
5use std::rc::Rc;
6
7use dom_struct::dom_struct;
8use js::gc::HandleValue;
9use js::jsapi::IsCallable;
10use rustc_hash::FxHashSet;
11use script_bindings::callback::ExceptionHandling;
12use script_bindings::codegen::GenericBindings::GeolocationBinding::Geolocation_Binding::GeolocationMethods;
13use script_bindings::codegen::GenericBindings::GeolocationBinding::{
14 PositionCallback, PositionErrorCallback, PositionOptions,
15};
16use script_bindings::codegen::GenericBindings::PermissionStatusBinding::PermissionName;
17use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
18use script_bindings::domstring::DOMString;
19use script_bindings::error::{Error, Fallible};
20use script_bindings::reflector::Reflector;
21use script_bindings::root::DomRoot;
22use script_bindings::script_runtime::CanGc;
23
24use crate::dom::bindings::codegen::DomTypeHolder::DomTypeHolder;
25use crate::dom::bindings::import::base::SafeJSContext;
26use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
27use crate::dom::geolocationpositionerror::GeolocationPositionError;
28use crate::dom::globalscope::GlobalScope;
29
30fn cast_error_callback(
31 cx: SafeJSContext,
32 error_callback: HandleValue,
33) -> Fallible<Option<Rc<PositionErrorCallback<DomTypeHolder>>>> {
34 if error_callback.get().is_object() {
35 let error_callback = error_callback.to_object();
36 #[expect(unsafe_code)]
37 unsafe {
38 if IsCallable(error_callback) {
39 Ok(Some(PositionErrorCallback::new(
40 SafeJSContext::from_ptr(cx.raw_cx()),
41 error_callback,
42 )))
43 } else {
44 Err(Error::Type(c"Value is not callable.".to_owned()))
45 }
46 }
47 } else if error_callback.get().is_null_or_undefined() {
48 Ok(None)
49 } else {
50 Err(Error::Type(c"Value is not an object.".to_owned()))
51 }
52}
53
54#[dom_struct]
55pub struct Geolocation {
56 reflector_: Reflector,
57 watch_ids: RefCell<FxHashSet<u32>>,
59 next_watch_id: Cell<u32>,
60}
61
62impl Geolocation {
63 fn new_inherited() -> Self {
64 Geolocation {
65 reflector_: Reflector::new(),
66 watch_ids: RefCell::new(FxHashSet::default()),
67 next_watch_id: Cell::new(1),
68 }
69 }
70
71 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
72 reflect_dom_object(Box::new(Self::new_inherited()), global, can_gc)
73 }
74
75 fn request_position(
77 &self,
78 _success_callback: Rc<PositionCallback<DomTypeHolder>>,
79 error_callback: Option<Rc<PositionErrorCallback<DomTypeHolder>>>,
80 _options: &PositionOptions,
81 watch_id: Option<u32>,
82 can_gc: CanGc,
83 ) -> Fallible<()> {
84 let document = self.global().as_window().Document();
87 if !document.allowed_to_use_feature(PermissionName::Geolocation) {
89 if let Some(id) = watch_id {
90 self.watch_ids.borrow_mut().remove(&id);
92 }
93 if let Some(error_callback) = error_callback {
95 error_callback.Call_(
96 self,
97 &GeolocationPositionError::permission_denied(
98 &self.global(),
99 DOMString::from("User denied Geolocation".to_string()),
100 can_gc,
101 ),
102 ExceptionHandling::Report,
103 can_gc,
104 )?;
105 }
106 return Ok(());
108 }
109 if !self.global().is_secure_context() {
111 if let Some(id) = watch_id {
112 self.watch_ids.borrow_mut().remove(&id);
114 }
115 if let Some(error_callback) = error_callback {
117 error_callback.Call_(
118 self,
119 &GeolocationPositionError::permission_denied(
120 &self.global(),
121 DOMString::from("Insecure context for Geolocation".to_string()),
122 can_gc,
123 ),
124 ExceptionHandling::Report,
125 can_gc,
126 )?;
127 }
128 return Ok(());
130 }
131 Ok(())
135 }
136}
137
138impl GeolocationMethods<DomTypeHolder> for Geolocation {
139 fn GetCurrentPosition(
141 &self,
142 context: SafeJSContext,
143 success_callback: Rc<PositionCallback<DomTypeHolder>>,
144 error_callback: HandleValue,
145 options: &PositionOptions,
146 can_gc: CanGc,
147 ) -> Fallible<()> {
148 let error_callback = cast_error_callback(context, error_callback)?;
149 if !self.global().as_window().Document().is_active() {
151 if let Some(error_callback) = error_callback {
153 error_callback.Call_(
154 self,
155 &GeolocationPositionError::position_unavailable(
156 &self.global(),
157 DOMString::from("Document is not fully active".to_string()),
158 can_gc,
159 ),
160 ExceptionHandling::Report,
161 can_gc,
162 )?;
163 }
164 return Ok(());
166 }
167 self.request_position(success_callback, error_callback, options, None, can_gc)
169 }
170
171 fn WatchPosition(
173 &self,
174 context: SafeJSContext,
175 success_callback: Rc<PositionCallback<DomTypeHolder>>,
176 error_callback: HandleValue,
177 options: &PositionOptions,
178 can_gc: CanGc,
179 ) -> Fallible<i32> {
180 let error_callback = cast_error_callback(context, error_callback)?;
181 if !self.global().as_window().Document().is_active() {
183 if let Some(error_callback) = error_callback {
185 error_callback.Call_(
186 self,
187 &GeolocationPositionError::position_unavailable(
188 &self.global(),
189 DOMString::from("Document is not fully active".to_string()),
190 can_gc,
191 ),
192 ExceptionHandling::Report,
193 can_gc,
194 )?;
195 }
196 return Ok(0);
198 }
199 let watch_id = self.next_watch_id.get();
201 self.next_watch_id.set(watch_id + 1);
202 self.watch_ids.borrow_mut().insert(watch_id);
204 self.request_position(
206 success_callback,
207 error_callback,
208 options,
209 Some(watch_id),
210 can_gc,
211 )?;
212 Ok(watch_id as i32)
214 }
215
216 fn ClearWatch(&self, watch_id: i32) {
218 let watch_id = u32::try_from(watch_id).ok();
219 if let Some(id) = watch_id {
220 self.watch_ids.borrow_mut().remove(&id);
221 }
222 }
223}