use std::cell::{Cell, RefCell};
use std::rc::Rc;
use dom_struct::dom_struct;
use js::gc::HandleValue;
use js::jsapi::IsCallable;
use rustc_hash::FxHashSet;
use script_bindings::callback::ExceptionHandling;
use script_bindings::codegen::GenericBindings::GeolocationBinding::Geolocation_Binding::GeolocationMethods;
use script_bindings::codegen::GenericBindings::GeolocationBinding::{
PositionCallback, PositionErrorCallback, PositionOptions,
};
use script_bindings::codegen::GenericBindings::PermissionStatusBinding::PermissionName;
use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
use script_bindings::domstring::DOMString;
use script_bindings::error::{Error, Fallible};
use script_bindings::reflector::Reflector;
use script_bindings::root::DomRoot;
use script_bindings::script_runtime::CanGc;
use crate::dom::bindings::codegen::DomTypeHolder::DomTypeHolder;
use crate::dom::bindings::import::base::SafeJSContext;
use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
use crate::dom::geolocationpositionerror::GeolocationPositionError;
use crate::dom::globalscope::GlobalScope;
fn cast_error_callback(
cx: SafeJSContext,
error_callback: HandleValue,
) -> Fallible<Option<Rc<PositionErrorCallback<DomTypeHolder>>>> {
if error_callback.get().is_object() {
let error_callback = error_callback.to_object();
#[expect(unsafe_code)]
unsafe {
if IsCallable(error_callback) {
Ok(Some(PositionErrorCallback::new(
SafeJSContext::from_ptr(cx.raw_cx()),
error_callback,
)))
} else {
Err(Error::Type(c"Value is not callable.".to_owned()))
}
}
} else if error_callback.get().is_null_or_undefined() {
Ok(None)
} else {
Err(Error::Type(c"Value is not an object.".to_owned()))
}
}
#[dom_struct]
pub struct Geolocation {
reflector_: Reflector,
watch_ids: RefCell<FxHashSet<u32>>,
next_watch_id: Cell<u32>,
}
impl Geolocation {
fn new_inherited() -> Self {
Geolocation {
reflector_: Reflector::new(),
watch_ids: RefCell::new(FxHashSet::default()),
next_watch_id: Cell::new(1),
}
}
pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
reflect_dom_object(Box::new(Self::new_inherited()), global, can_gc)
}
fn request_position(
&self,
_success_callback: Rc<PositionCallback<DomTypeHolder>>,
error_callback: Option<Rc<PositionErrorCallback<DomTypeHolder>>>,
_options: &PositionOptions,
watch_id: Option<u32>,
can_gc: CanGc,
) -> Fallible<()> {
let document = self.global().as_window().Document();
if !document.allowed_to_use_feature(PermissionName::Geolocation) {
if let Some(id) = watch_id {
self.watch_ids.borrow_mut().remove(&id);
}
if let Some(error_callback) = error_callback {
error_callback.Call_(
self,
&GeolocationPositionError::permission_denied(
&self.global(),
DOMString::from("User denied Geolocation".to_string()),
can_gc,
),
ExceptionHandling::Report,
can_gc,
)?;
}
return Ok(());
}
if !self.global().is_secure_context() {
if let Some(id) = watch_id {
self.watch_ids.borrow_mut().remove(&id);
}
if let Some(error_callback) = error_callback {
error_callback.Call_(
self,
&GeolocationPositionError::permission_denied(
&self.global(),
DOMString::from("Insecure context for Geolocation".to_string()),
can_gc,
),
ExceptionHandling::Report,
can_gc,
)?;
}
return Ok(());
}
Ok(())
}
}
impl GeolocationMethods<DomTypeHolder> for Geolocation {
fn GetCurrentPosition(
&self,
context: SafeJSContext,
success_callback: Rc<PositionCallback<DomTypeHolder>>,
error_callback: HandleValue,
options: &PositionOptions,
can_gc: CanGc,
) -> Fallible<()> {
let error_callback = cast_error_callback(context, error_callback)?;
if !self.global().as_window().Document().is_active() {
if let Some(error_callback) = error_callback {
error_callback.Call_(
self,
&GeolocationPositionError::position_unavailable(
&self.global(),
DOMString::from("Document is not fully active".to_string()),
can_gc,
),
ExceptionHandling::Report,
can_gc,
)?;
}
return Ok(());
}
self.request_position(success_callback, error_callback, options, None, can_gc)
}
fn WatchPosition(
&self,
context: SafeJSContext,
success_callback: Rc<PositionCallback<DomTypeHolder>>,
error_callback: HandleValue,
options: &PositionOptions,
can_gc: CanGc,
) -> Fallible<i32> {
let error_callback = cast_error_callback(context, error_callback)?;
if !self.global().as_window().Document().is_active() {
if let Some(error_callback) = error_callback {
error_callback.Call_(
self,
&GeolocationPositionError::position_unavailable(
&self.global(),
DOMString::from("Document is not fully active".to_string()),
can_gc,
),
ExceptionHandling::Report,
can_gc,
)?;
}
return Ok(0);
}
let watch_id = self.next_watch_id.get();
self.next_watch_id.set(watch_id + 1);
self.watch_ids.borrow_mut().insert(watch_id);
self.request_position(
success_callback,
error_callback,
options,
Some(watch_id),
can_gc,
)?;
Ok(watch_id as i32)
}
fn ClearWatch(&self, watch_id: i32) {
let watch_id = u32::try_from(watch_id).ok();
if let Some(id) = watch_id {
self.watch_ids.borrow_mut().remove(&id);
}
}
}