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};
22use script_bindings::root::DomRoot;
23use script_bindings::script_runtime::CanGc;
24
25use crate::dom::bindings::codegen::DomTypeHolder::DomTypeHolder;
26use crate::dom::bindings::import::base::SafeJSContext;
27use crate::dom::bindings::reflector::DomGlobal;
28use crate::dom::geolocationpositionerror::GeolocationPositionError;
29use crate::dom::globalscope::GlobalScope;
30
31fn cast_error_callback(
32 cx: SafeJSContext,
33 error_callback: HandleValue,
34) -> Fallible<Option<Rc<PositionErrorCallback<DomTypeHolder>>>> {
35 if error_callback.get().is_object() {
36 let error_callback = error_callback.to_object();
37 #[expect(unsafe_code)]
38 unsafe {
39 if IsCallable(error_callback) {
40 Ok(Some(PositionErrorCallback::new(
41 SafeJSContext::from_ptr(cx.raw_cx()),
42 error_callback,
43 )))
44 } else {
45 Err(Error::Type(c"Value is not callable.".to_owned()))
46 }
47 }
48 } else if error_callback.get().is_null_or_undefined() {
49 Ok(None)
50 } else {
51 Err(Error::Type(c"Value is not an object.".to_owned()))
52 }
53}
54
55#[dom_struct]
56pub struct Geolocation {
57 reflector_: Reflector,
58 watch_ids: RefCell<FxHashSet<u32>>,
60 next_watch_id: Cell<u32>,
61}
62
63impl Geolocation {
64 fn new_inherited() -> Self {
65 Geolocation {
66 reflector_: Reflector::new(),
67 watch_ids: RefCell::new(FxHashSet::default()),
68 next_watch_id: Cell::new(1),
69 }
70 }
71
72 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
73 reflect_dom_object(Box::new(Self::new_inherited()), global, can_gc)
74 }
75
76 fn request_position(
78 &self,
79 cx: &mut JSContext,
80 _success_callback: Rc<PositionCallback<DomTypeHolder>>,
81 error_callback: Option<Rc<PositionErrorCallback<DomTypeHolder>>>,
82 _options: &PositionOptions,
83 watch_id: Option<u32>,
84 ) -> Fallible<()> {
85 let document = self.global().as_window().Document();
88 if !document.allowed_to_use_feature(PermissionName::Geolocation) {
90 if let Some(id) = watch_id {
91 self.watch_ids.borrow_mut().remove(&id);
93 }
94 if let Some(error_callback) = error_callback {
96 let position_error = GeolocationPositionError::permission_denied(
97 &self.global(),
98 DOMString::from("User denied Geolocation".to_string()),
99 CanGc::from_cx(cx),
100 );
101 error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
102 }
103 return Ok(());
105 }
106 if !self.global().is_secure_context() {
108 if let Some(id) = watch_id {
109 self.watch_ids.borrow_mut().remove(&id);
111 }
112 if let Some(error_callback) = error_callback {
114 let position_error = GeolocationPositionError::permission_denied(
115 &self.global(),
116 DOMString::from("Insecure context for Geolocation".to_string()),
117 CanGc::from_cx(cx),
118 );
119 error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
120 }
121 return Ok(());
123 }
124 Ok(())
128 }
129}
130
131impl GeolocationMethods<DomTypeHolder> for Geolocation {
132 fn GetCurrentPosition(
134 &self,
135 cx: &mut JSContext,
136 success_callback: Rc<PositionCallback<DomTypeHolder>>,
137 error_callback: HandleValue,
138 options: &PositionOptions,
139 ) -> Fallible<()> {
140 let error_callback = cast_error_callback(cx.into(), error_callback)?;
141 if !self.global().as_window().Document().is_active() {
143 if let Some(error_callback) = error_callback {
145 let position_error = GeolocationPositionError::position_unavailable(
146 &self.global(),
147 DOMString::from("Document is not fully active".to_string()),
148 CanGc::from_cx(cx),
149 );
150 error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
151 }
152 return Ok(());
154 }
155 self.request_position(cx, success_callback, error_callback, options, None)
157 }
158
159 fn WatchPosition(
161 &self,
162 cx: &mut JSContext,
163 success_callback: Rc<PositionCallback<DomTypeHolder>>,
164 error_callback: HandleValue,
165 options: &PositionOptions,
166 ) -> Fallible<i32> {
167 let error_callback = cast_error_callback(cx.into(), error_callback)?;
168 if !self.global().as_window().Document().is_active() {
170 if let Some(error_callback) = error_callback {
172 let position_error = GeolocationPositionError::position_unavailable(
173 &self.global(),
174 DOMString::from("Document is not fully active".to_string()),
175 CanGc::from_cx(cx),
176 );
177 error_callback.Call_(cx, self, &position_error, ExceptionHandling::Report)?;
178 }
179 return Ok(0);
181 }
182 let watch_id = self.next_watch_id.get();
184 self.next_watch_id.set(watch_id + 1);
185 self.watch_ids.borrow_mut().insert(watch_id);
187 self.request_position(
189 cx,
190 success_callback,
191 error_callback,
192 options,
193 Some(watch_id),
194 )?;
195 Ok(watch_id as i32)
197 }
198
199 fn ClearWatch(&self, watch_id: i32) {
201 let watch_id = u32::try_from(watch_id).ok();
202 if let Some(id) = watch_id {
203 self.watch_ids.borrow_mut().remove(&id);
204 }
205 }
206}