script/dom/geolocation/
geolocation.rs1use 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;
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::import::base::SafeJSContext;
26use crate::dom::bindings::reflector::DomGlobal;
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(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Self> {
72 reflect_dom_object_with_cx(Box::new(Self::new_inherited()), global, cx)
73 }
74
75 fn request_position(
77 &self,
78 cx: &mut JSContext,
79 _success_callback: Rc<PositionCallback<DomTypeHolder>>,
80 error_callback: Option<Rc<PositionErrorCallback<DomTypeHolder>>>,
81 _options: &PositionOptions,
82 watch_id: Option<u32>,
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 let position_error = GeolocationPositionError::permission_denied(
96 cx,
97 &self.global(),
98 DOMString::from("User denied Geolocation".to_string()),
99 );
100 error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
101 }
102 return Ok(());
104 }
105 if !self.global().is_secure_context() {
107 if let Some(id) = watch_id {
108 self.watch_ids.borrow_mut().remove(&id);
110 }
111 if let Some(error_callback) = error_callback {
113 let position_error = GeolocationPositionError::permission_denied(
114 cx,
115 &self.global(),
116 DOMString::from("Insecure context for Geolocation".to_string()),
117 );
118 error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
119 }
120 return Ok(());
122 }
123 Ok(())
127 }
128}
129
130impl GeolocationMethods<DomTypeHolder> for Geolocation {
131 fn GetCurrentPosition(
133 &self,
134 cx: &mut JSContext,
135 success_callback: Rc<PositionCallback<DomTypeHolder>>,
136 error_callback: HandleValue,
137 options: &PositionOptions,
138 ) -> Fallible<()> {
139 let error_callback = cast_error_callback(cx.into(), error_callback)?;
140 if !self.global().as_window().Document().is_active() {
142 if let Some(error_callback) = error_callback {
144 let position_error = GeolocationPositionError::position_unavailable(
145 cx,
146 &self.global(),
147 DOMString::from("Document is not fully active".to_string()),
148 );
149 error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
150 }
151 return Ok(());
153 }
154 self.request_position(cx, success_callback, error_callback, options, None)
156 }
157
158 fn WatchPosition(
160 &self,
161 cx: &mut JSContext,
162 success_callback: Rc<PositionCallback<DomTypeHolder>>,
163 error_callback: HandleValue,
164 options: &PositionOptions,
165 ) -> Fallible<i32> {
166 let error_callback = cast_error_callback(cx.into(), error_callback)?;
167 if !self.global().as_window().Document().is_active() {
169 if let Some(error_callback) = error_callback {
171 let position_error = GeolocationPositionError::position_unavailable(
172 cx,
173 &self.global(),
174 DOMString::from("Document is not fully active".to_string()),
175 );
176 error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
177 }
178 return Ok(0);
180 }
181 let watch_id = self.next_watch_id.get();
183 self.next_watch_id.set(watch_id + 1);
184 self.watch_ids.borrow_mut().insert(watch_id);
186 self.request_position(
188 cx,
189 success_callback,
190 error_callback,
191 options,
192 Some(watch_id),
193 )?;
194 Ok(watch_id as i32)
196 }
197
198 fn ClearWatch(&self, watch_id: i32) {
200 let watch_id = u32::try_from(watch_id).ok();
201 if let Some(id) = watch_id {
202 self.watch_ids.borrow_mut().remove(&id);
203 }
204 }
205}