use std::cell::Cell;
use std::convert::TryInto;
use std::sync::LazyLock;
use dom_struct::dom_struct;
use js::rust::MutableHandleValue;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::utils::to_frozen_array;
use crate::dom::bluetooth::Bluetooth;
use crate::dom::gamepad::Gamepad;
use crate::dom::gamepadevent::GamepadEventType;
use crate::dom::mediadevices::MediaDevices;
use crate::dom::mediasession::MediaSession;
use crate::dom::mimetypearray::MimeTypeArray;
use crate::dom::navigatorinfo;
use crate::dom::permissions::Permissions;
use crate::dom::pluginarray::PluginArray;
use crate::dom::serviceworkercontainer::ServiceWorkerContainer;
#[cfg(feature = "webgpu")]
use crate::dom::webgpu::gpu::GPU;
use crate::dom::window::Window;
#[cfg(feature = "webxr")]
use crate::dom::xrsystem::XRSystem;
use crate::script_runtime::{CanGc, JSContext};
pub(super) fn hardware_concurrency() -> u64 {
static CPUS: LazyLock<u64> = LazyLock::new(|| num_cpus::get().try_into().unwrap_or(1));
*CPUS
}
#[dom_struct]
pub struct Navigator {
reflector_: Reflector,
bluetooth: MutNullableDom<Bluetooth>,
plugins: MutNullableDom<PluginArray>,
mime_types: MutNullableDom<MimeTypeArray>,
service_worker: MutNullableDom<ServiceWorkerContainer>,
#[cfg(feature = "webxr")]
xr: MutNullableDom<XRSystem>,
mediadevices: MutNullableDom<MediaDevices>,
gamepads: DomRefCell<Vec<MutNullableDom<Gamepad>>>,
permissions: MutNullableDom<Permissions>,
mediasession: MutNullableDom<MediaSession>,
#[cfg(feature = "webgpu")]
gpu: MutNullableDom<GPU>,
has_gamepad_gesture: Cell<bool>,
}
impl Navigator {
fn new_inherited() -> Navigator {
Navigator {
reflector_: Reflector::new(),
bluetooth: Default::default(),
plugins: Default::default(),
mime_types: Default::default(),
service_worker: Default::default(),
#[cfg(feature = "webxr")]
xr: Default::default(),
mediadevices: Default::default(),
gamepads: Default::default(),
permissions: Default::default(),
mediasession: Default::default(),
#[cfg(feature = "webgpu")]
gpu: Default::default(),
has_gamepad_gesture: Cell::new(false),
}
}
pub fn new(window: &Window) -> DomRoot<Navigator> {
reflect_dom_object(Box::new(Navigator::new_inherited()), window, CanGc::note())
}
#[cfg(feature = "webxr")]
pub fn xr(&self) -> Option<DomRoot<XRSystem>> {
self.xr.get()
}
pub fn get_gamepad(&self, index: usize) -> Option<DomRoot<Gamepad>> {
self.gamepads.borrow().get(index).and_then(|g| g.get())
}
pub fn set_gamepad(&self, index: usize, gamepad: &Gamepad, can_gc: CanGc) {
if let Some(gamepad_to_set) = self.gamepads.borrow().get(index) {
gamepad_to_set.set(Some(gamepad));
}
if self.has_gamepad_gesture.get() {
gamepad.set_exposed(true);
if self.global().as_window().Document().is_fully_active() {
gamepad.notify_event(GamepadEventType::Connected, can_gc);
}
}
}
pub fn remove_gamepad(&self, index: usize) {
if let Some(gamepad_to_remove) = self.gamepads.borrow_mut().get(index) {
gamepad_to_remove.set(None);
}
self.shrink_gamepads_list();
}
pub fn select_gamepad_index(&self) -> u32 {
let mut gamepad_list = self.gamepads.borrow_mut();
if let Some(index) = gamepad_list.iter().position(|g| g.get().is_none()) {
index as u32
} else {
let len = gamepad_list.len();
gamepad_list.resize_with(len + 1, Default::default);
len as u32
}
}
fn shrink_gamepads_list(&self) {
let mut gamepad_list = self.gamepads.borrow_mut();
for i in (0..gamepad_list.len()).rev() {
if gamepad_list.get(i).is_none() {
gamepad_list.remove(i);
} else {
break;
}
}
}
pub fn has_gamepad_gesture(&self) -> bool {
self.has_gamepad_gesture.get()
}
pub fn set_has_gamepad_gesture(&self, has_gamepad_gesture: bool) {
self.has_gamepad_gesture.set(has_gamepad_gesture);
}
}
impl NavigatorMethods<crate::DomTypeHolder> for Navigator {
fn Product(&self) -> DOMString {
navigatorinfo::Product()
}
fn ProductSub(&self) -> DOMString {
navigatorinfo::ProductSub()
}
fn Vendor(&self) -> DOMString {
navigatorinfo::Vendor()
}
fn VendorSub(&self) -> DOMString {
navigatorinfo::VendorSub()
}
fn TaintEnabled(&self) -> bool {
navigatorinfo::TaintEnabled()
}
fn AppName(&self) -> DOMString {
navigatorinfo::AppName()
}
fn AppCodeName(&self) -> DOMString {
navigatorinfo::AppCodeName()
}
fn Platform(&self) -> DOMString {
navigatorinfo::Platform()
}
fn UserAgent(&self) -> DOMString {
navigatorinfo::UserAgent(self.global().get_user_agent())
}
fn AppVersion(&self) -> DOMString {
navigatorinfo::AppVersion()
}
fn Bluetooth(&self) -> DomRoot<Bluetooth> {
self.bluetooth.or_init(|| Bluetooth::new(&self.global()))
}
fn Language(&self) -> DOMString {
navigatorinfo::Language()
}
#[allow(unsafe_code)]
fn Languages(&self, cx: JSContext, retval: MutableHandleValue) {
to_frozen_array(&[self.Language()], cx, retval)
}
fn Plugins(&self) -> DomRoot<PluginArray> {
self.plugins.or_init(|| PluginArray::new(&self.global()))
}
fn MimeTypes(&self) -> DomRoot<MimeTypeArray> {
self.mime_types
.or_init(|| MimeTypeArray::new(&self.global()))
}
fn JavaEnabled(&self) -> bool {
false
}
fn ServiceWorker(&self) -> DomRoot<ServiceWorkerContainer> {
self.service_worker
.or_init(|| ServiceWorkerContainer::new(&self.global()))
}
fn CookieEnabled(&self) -> bool {
true
}
fn GetGamepads(&self) -> Vec<Option<DomRoot<Gamepad>>> {
let global = self.global();
let window = global.as_window();
let doc = window.Document();
if !doc.is_fully_active() || !self.has_gamepad_gesture.get() {
return Vec::new();
}
self.gamepads.borrow().iter().map(|g| g.get()).collect()
}
fn Permissions(&self) -> DomRoot<Permissions> {
self.permissions
.or_init(|| Permissions::new(&self.global()))
}
#[cfg(feature = "webxr")]
fn Xr(&self) -> DomRoot<XRSystem> {
self.xr.or_init(|| XRSystem::new(self.global().as_window()))
}
fn MediaDevices(&self) -> DomRoot<MediaDevices> {
self.mediadevices
.or_init(|| MediaDevices::new(&self.global()))
}
fn MediaSession(&self) -> DomRoot<MediaSession> {
self.mediasession.or_init(|| {
let global = self.global();
let window = global.as_window();
MediaSession::new(window)
})
}
#[cfg(feature = "webgpu")]
fn Gpu(&self) -> DomRoot<GPU> {
self.gpu.or_init(|| GPU::new(&self.global()))
}
fn HardwareConcurrency(&self) -> u64 {
hardware_concurrency()
}
}