From 586255ac0ad29ad9dc101a680fb504f15c979da8 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Sun, 4 Aug 2024 16:49:19 +0200 Subject: [PATCH] Web: monitor API improvements (#3847) - Improved the documentation to point users into the right direction from all kinds of methods and types. - De-duplicated some code and added more comments. - Implement an ID system to correctly and efficiently implement `Eq`, `Hash`, `Ord`, `PartialEq` and `PartialOrd`. - Fixed screen locking support being cached thread local, ergo calling from a different thread would require to make the check again, not fulfilling its purpose as a fast-path at all. --- src/monitor.rs | 17 + src/platform/web.rs | 23 +- .../web/event_loop/window_target.rs | 2 +- src/platform_impl/web/monitor.rs | 423 +++++++++++------- 4 files changed, 294 insertions(+), 171 deletions(-) diff --git a/src/monitor.rs b/src/monitor.rs index c4f067e035..d66a54bc8c 100644 --- a/src/monitor.rs +++ b/src/monitor.rs @@ -98,6 +98,23 @@ impl std::fmt::Display for VideoModeHandle { /// /// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation. /// +/// ## Platform-specific +/// +/// **Web:** A [`MonitorHandle`] created without +#[cfg_attr( + any(web_platform, docsrs), + doc = "[detailed monitor permissions][crate::platform::web::ActiveEventLoopExtWeb::request_detailed_monitor_permission]." +)] +#[cfg_attr(not(any(web_platform, docsrs)), doc = "detailed monitor permissions.")] +/// will always represent the current monitor the browser window is in instead of a specific +/// monitor. See +#[cfg_attr( + any(web_platform, docsrs), + doc = "[`MonitorHandleExtWeb::is_detailed()`][crate::platform::web::MonitorHandleExtWeb::is_detailed]" +)] +#[cfg_attr(not(any(web_platform, docsrs)), doc = "`MonitorHandleExtWeb::is_detailed()`")] +/// to check. +/// /// [`Window`]: crate::window::Window #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct MonitorHandle { diff --git a/src/platform/web.rs b/src/platform/web.rs index 61faf39bd1..4e96820134 100644 --- a/src/platform/web.rs +++ b/src/platform/web.rs @@ -226,7 +226,8 @@ pub trait EventLoopExtWeb { /// [`ControlFlow::WaitUntil`]: crate::event_loop::ControlFlow::WaitUntil fn wait_until_strategy(&self) -> WaitUntilStrategy; - /// Returns if the users device has multiple screens. + /// Returns if the users device has multiple screens. Useful to check before prompting the user + /// with [`EventLoopExtWeb::request_detailed_monitor_permission()`]. /// /// Browsers might always return [`false`] to reduce fingerprinting. fn has_multiple_screens(&self) -> Result; @@ -234,11 +235,17 @@ pub trait EventLoopExtWeb { /// Prompts the user for permission to query detailed information about available monitors. The /// returned [`MonitorPermissionFuture`] can be dropped without aborting the request. /// + /// Check [`EventLoopExtWeb::has_multiple_screens()`] before unnecessarily prompting the user + /// for such permissions. + /// /// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New /// [`MonitorHandle`]s have to be created instead. fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture; /// Returns whether the user has given permission to access detailed monitor information. + /// + /// [`MonitorHandle`]s don't automatically make use of detailed monitor information after + /// permission is granted. New [`MonitorHandle`]s have to be created instead. fn has_detailed_monitor_permission(&self) -> HasMonitorPermissionFuture; } @@ -314,7 +321,8 @@ pub trait ActiveEventLoopExtWeb { /// [`CursorGrabMode::Locked`]: crate::window::CursorGrabMode::Locked fn is_cursor_lock_raw(&self) -> bool; - /// Returns if the users device has multiple screens. + /// Returns if the users device has multiple screens. Useful to check before prompting the user + /// with [`EventLoopExtWeb::request_detailed_monitor_permission()`]. /// /// Browsers might always return [`false`] to reduce fingerprinting. fn has_multiple_screens(&self) -> Result; @@ -322,14 +330,17 @@ pub trait ActiveEventLoopExtWeb { /// Prompts the user for permission to query detailed information about available monitors. The /// returned [`MonitorPermissionFuture`] can be dropped without aborting the request. /// + /// Check [`EventLoopExtWeb::has_multiple_screens()`] before unnecessarily prompting the user + /// for such permissions. + /// /// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New /// [`MonitorHandle`]s have to be created instead. fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture; /// Returns whether the user has given permission to access detailed monitor information. /// - /// [`MonitorHandle`]s don't automatically make use of this after permission is granted. New - /// [`MonitorHandle`]s have to be created instead. + /// [`MonitorHandle`]s don't automatically make use of detailed monitor information after + /// permission is granted. New [`MonitorHandle`]s have to be created instead. fn has_detailed_monitor_permission(&self) -> bool; } @@ -608,7 +619,9 @@ pub trait MonitorHandleExtWeb { /// Will fail if a locking call is in progress. fn unlock(&self) -> Result<(), OrientationLockError>; - /// Returns whether this [`MonitorHandle`] was created using detailed monitor permissions. + /// Returns whether this [`MonitorHandle`] was created using detailed monitor permissions. If + /// [`false`] will always represent the current monitor the browser window is in instead of a + /// specific monitor. /// /// See [`ActiveEventLoop::request_detailed_monitor_permission()`]. fn is_detailed(&self) -> bool; diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index c6ee2f41ae..e4e92f4e23 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -652,7 +652,7 @@ impl ActiveEventLoop { } pub(crate) fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture { - self.runner.monitor().request_detailed_monitor_permission(self.runner.weak()) + self.runner.monitor().request_detailed_monitor_permission() } pub(crate) fn has_detailed_monitor_permission(&self) -> bool { diff --git a/src/platform_impl/web/monitor.rs b/src/platform_impl/web/monitor.rs index ccd6b44b9f..8decd2a00a 100644 --- a/src/platform_impl/web/monitor.rs +++ b/src/platform_impl/web/monitor.rs @@ -1,10 +1,13 @@ use std::cell::{OnceCell, Ref, RefCell}; +use std::cmp::Ordering; use std::future::Future; -use std::hash::Hash; +use std::hash::{Hash, Hasher}; use std::iter::{self, Once}; use std::mem; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; use std::pin::Pin; +use std::rc::{Rc, Weak}; +use std::sync::OnceLock; use std::task::{ready, Context, Poll}; use dpi::LogicalSize; @@ -28,24 +31,29 @@ use crate::platform::web::{ MonitorPermissionError, Orientation, OrientationData, OrientationLock, OrientationLockError, }; -#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub struct MonitorHandle(Dispatcher); +#[derive(Debug, Clone, Eq)] +pub struct MonitorHandle { + /// [`None`] means [`web_sys::Screen`], which is always the same. + id: Option, + inner: Dispatcher, +} impl MonitorHandle { fn new(main_thread: MainThreadMarker, inner: Inner) -> Self { - Self(Dispatcher::new(main_thread, inner).0) + let id = if let Screen::Detailed { id, .. } = inner.screen { Some(id) } else { None }; + Self { id, inner: Dispatcher::new(main_thread, inner).0 } } pub fn scale_factor(&self) -> f64 { - self.0.queue(|inner| match &inner.screen { + self.inner.queue(|inner| match &inner.screen { Screen::Screen(_) => 0., - Screen::Detailed(screen) => screen.device_pixel_ratio(), + Screen::Detailed { screen, .. } => screen.device_pixel_ratio(), }) } pub fn position(&self) -> PhysicalPosition { - self.0.queue(|inner| { - if let Screen::Detailed(screen) = &inner.screen { + self.inner.queue(|inner| { + if let Screen::Detailed { screen, .. } = &inner.screen { PhysicalPosition::new(screen.left(), screen.top()) } else { PhysicalPosition::default() @@ -54,8 +62,8 @@ impl MonitorHandle { } pub fn name(&self) -> Option { - self.0.queue(|inner| { - if let Screen::Detailed(screen) = &inner.screen { + self.inner.queue(|inner| { + if let Screen::Detailed { screen, .. } = &inner.screen { Some(screen.label()) } else { None @@ -68,7 +76,7 @@ impl MonitorHandle { } pub fn size(&self) -> PhysicalSize { - self.0.queue(|inner| { + self.inner.queue(|inner| { let width = inner.screen.width().unwrap(); let height = inner.screen.height().unwrap(); @@ -86,9 +94,8 @@ impl MonitorHandle { } pub fn orientation(&self) -> OrientationData { - self.0.queue(|inner| { - let orientation = - inner.orientation.get_or_init(|| inner.screen.orientation().unchecked_into()); + self.inner.queue(|inner| { + let orientation = inner.orientation(); let angle = orientation.angle().unwrap(); match orientation.type_().unwrap() { @@ -121,23 +128,19 @@ impl MonitorHandle { pub fn request_lock(&self, orientation_lock: OrientationLock) -> OrientationLockFuture { // Short-circuit without blocking. - if let Some(support) = HAS_LOCK_SUPPORT.with(|support| support.get().cloned()) { + if let Some(support) = has_previous_lock_support() { if !support { return OrientationLockFuture::Ready(Some(Err(OrientationLockError::Unsupported))); } } - self.0.queue(|inner| { - let orientation = - inner.orientation.get_or_init(|| inner.screen.orientation().unchecked_into()); - - if !HAS_LOCK_SUPPORT - .with(|support| *support.get_or_init(|| !orientation.has_lock().is_undefined())) - { + self.inner.queue(|inner| { + if !inner.has_lock_support() { return OrientationLockFuture::Ready(Some(Err(OrientationLockError::Unsupported))); } - let future = JsFuture::from(orientation.lock(orientation_lock.to_js()).unwrap()); + let future = + JsFuture::from(inner.orientation().lock(orientation_lock.to_js()).unwrap()); let notifier = Notifier::new(); let notified = notifier.notified(); @@ -151,29 +154,24 @@ impl MonitorHandle { pub fn unlock(&self) -> Result<(), OrientationLockError> { // Short-circuit without blocking. - if let Some(support) = HAS_LOCK_SUPPORT.with(|support| support.get().cloned()) { + if let Some(support) = has_previous_lock_support() { if !support { return Err(OrientationLockError::Unsupported); } } - self.0.queue(|inner| { - let orientation = - inner.orientation.get_or_init(|| inner.screen.orientation().unchecked_into()); - - if !HAS_LOCK_SUPPORT - .with(|support| *support.get_or_init(|| !orientation.has_lock().is_undefined())) - { + self.inner.queue(|inner| { + if !inner.has_lock_support() { return Err(OrientationLockError::Unsupported); } - orientation.unlock().map_err(OrientationLockError::from_js) + inner.orientation().unlock().map_err(OrientationLockError::from_js) }) } pub fn is_internal(&self) -> Option { - self.0.queue(|inner| { - if let Screen::Detailed(screen) = &inner.screen { + self.inner.queue(|inner| { + if let Screen::Detailed { screen, .. } = &inner.screen { Some(screen.is_internal()) } else { None @@ -182,19 +180,19 @@ impl MonitorHandle { } pub fn is_detailed(&self) -> bool { - self.0.queue(|inner| matches!(inner.screen, Screen::Detailed(_))) + self.inner.queue(|inner| matches!(inner.screen, Screen::Detailed { .. })) } pub(crate) fn detailed( &self, main_thread: MainThreadMarker, ) -> Option> { - let inner = self.0.value(main_thread); + let inner = self.inner.value(main_thread); match &inner.screen { Screen::Screen(_) => None, - Screen::Detailed(_) => Some(Ref::map(inner, |inner| { - if let Screen::Detailed(detailed) = &inner.screen { - detailed + Screen::Detailed { .. } => Some(Ref::map(inner, |inner| { + if let Screen::Detailed { screen, .. } = &inner.screen { + screen.deref() } else { unreachable!() } @@ -203,6 +201,30 @@ impl MonitorHandle { } } +impl Hash for MonitorHandle { + fn hash(&self, state: &mut H) { + self.id.hash(state) + } +} + +impl Ord for MonitorHandle { + fn cmp(&self, other: &Self) -> Ordering { + self.id.cmp(&other.id) + } +} + +impl PartialEq for MonitorHandle { + fn eq(&self, other: &Self) -> bool { + self.id.eq(&other.id) + } +} + +impl PartialOrd for MonitorHandle { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + #[derive(Debug)] pub enum OrientationLockFuture { Future(Notified>), @@ -269,7 +291,7 @@ impl VideoModeHandle { } pub fn bit_depth(&self) -> u16 { - self.0 .0.queue(|inner| inner.screen.color_depth().unwrap()).try_into().unwrap() + self.0.inner.queue(|inner| inner.screen.color_depth().unwrap()).try_into().unwrap() } pub fn refresh_rate_millihertz(&self) -> u32 { @@ -292,11 +314,37 @@ impl Inner { fn new(window: WindowExt, engine: Option, screen: Screen) -> Self { Self { window, engine, screen, orientation: OnceCell::new() } } + + fn orientation(&self) -> &ScreenOrientationExt { + self.orientation.get_or_init(|| self.screen.orientation().unchecked_into()) + } + + fn has_lock_support(&self) -> bool { + *HAS_LOCK_SUPPORT.get_or_init(|| !self.orientation().has_lock().is_undefined()) + } +} + +impl Drop for Inner { + fn drop(&mut self) { + if let Screen::Detailed { runner, id, screen } = &self.screen { + // If this is the last screen with its ID, clean it up in the `MonitorHandler`. + if Rc::strong_count(screen) == 1 { + if let Some(runner) = runner.upgrade() { + let mut state = runner.monitor().state.borrow_mut(); + let State::Detailed(detailed) = state.deref_mut() else { + unreachable!("found a `ScreenDetailed` without being in `State::Detailed`") + }; + + detailed.screens.retain(|(id_internal, _)| *id_internal != *id) + } + } + } + } } enum Screen { Screen(ScreenExt), - Detailed(ScreenDetailed), + Detailed { runner: WeakShared, id: u64, screen: Rc }, } impl Deref for Screen { @@ -305,12 +353,13 @@ impl Deref for Screen { fn deref(&self) -> &Self::Target { match self { Screen::Screen(screen) => screen, - Screen::Detailed(screen) => screen, + Screen::Detailed { screen, .. } => screen, } } } pub struct MonitorHandler { + runner: WeakShared, state: RefCell, main_thread: MainThreadMarker, window: WindowExt, @@ -323,10 +372,60 @@ enum State { Initialize(Notified>), Permission { permission: PermissionStatusExt, _handle: EventListenerHandle }, Upgrade(Notified>), - Detailed(ScreenDetails), + Detailed(Detailed), +} + +struct Detailed { + details: ScreenDetails, + id_counter: u64, + screens: Vec<(u64, Weak)>, +} + +impl Detailed { + fn handle( + &mut self, + main_thread: MainThreadMarker, + runner: WeakShared, + window: WindowExt, + engine: Option, + screen: ScreenDetailed, + ) -> MonitorHandle { + // Before creating a new entry, see if we have an ID for this screen already. + let found_screen = self.screens.iter().find_map(|(id, internal_screen)| { + let internal_screen = + internal_screen.upgrade().expect("dropped `MonitorHandle` without cleaning up"); + + if *internal_screen == screen { + Some((*id, internal_screen)) + } else { + None + } + }); + let (id, screen) = if let Some((id, screen)) = found_screen { + (id, screen) + } else { + let id = self.id_counter; + self.id_counter += 1; + let screen = Rc::new(screen); + + self.screens.push((id, Rc::downgrade(&screen))); + + (id, screen) + }; + + MonitorHandle::new( + main_thread, + Inner::new(window, engine, Screen::Detailed { runner, id, screen }), + ) + } } impl MonitorHandler { + /// When the [`MonitorHandler`] is created, it first checks if permission has already been + /// granted by the user for this page, in which case it retrieves [`ScreenDetails`]. + /// + /// If not, it will listen to external changes in the permission and automatically elevate + /// [`MonitorHandler`]. pub fn new( main_thread: MainThreadMarker, window: Window, @@ -338,6 +437,7 @@ impl MonitorHandler { let screen: ScreenExt = window.screen().unwrap().unchecked_into(); let state = if has_screen_details_support(&window) { + // First try and get permissions. let permissions = navigator.permissions().expect( "expected the Permissions API to be implemented if the Window Management API is \ as well", @@ -346,6 +446,7 @@ impl MonitorHandler { descriptor.set_name("window-management"); let future = JsFuture::from(permissions.query(&descriptor).unwrap()); + let runner = runner.clone(); let window = window.clone(); let notifier = Notifier::new(); let notified = notifier.notified(); @@ -359,17 +460,18 @@ impl MonitorHandler { ), }; - let screen_details = match permission.state() { + let details = match permission.state() { + // If we have permission, go ahead and get `ScreenDetails`. PermissionState::Granted => { - let screen_details = match JsFuture::from(window.screen_details()).await { - Ok(screen_details) => screen_details.unchecked_into(), + let details = match JsFuture::from(window.screen_details()).await { + Ok(details) => details.unchecked_into(), Err(error) => unreachable_error( &error, "getting screen details failed even though permission was granted", ), }; notifier.notify(Ok(())); - Some(screen_details) + Some(details) }, PermissionState::Denied => { notifier.notify(Err(MonitorPermissionError::Denied)); @@ -392,17 +494,17 @@ impl MonitorHandler { // Notifying `Future`s is not dependant on the lifetime of the runner, // because they can outlive it. if let Some(runner) = runner.upgrade() { - let state = if let Some(screen_details) = screen_details { - State::Detailed(screen_details) + if let Some(details) = details { + runner.monitor().upgrade(details); } else { // If permission is denied we listen for changes so we can catch external // permission granting. let handle = Self::setup_listener(runner.weak(), window, permission.clone()); - State::Permission { permission, _handle: handle } + *runner.monitor().state.borrow_mut() = + State::Permission { permission, _handle: handle }; }; - *runner.monitor().state.borrow_mut() = state; runner.start_delayed(); } }); @@ -412,9 +514,10 @@ impl MonitorHandler { State::Unsupported }; - Self { state: RefCell::new(state), main_thread, window, engine, screen } + Self { runner, state: RefCell::new(state), main_thread, window, engine, screen } } + /// Listens to external permission changes and elevates [`MonitorHandle`] automatically. fn setup_listener( runner: WeakShared, window: WindowExt, @@ -429,8 +532,8 @@ impl MonitorHandler { let runner = runner.clone(); wasm_bindgen_futures::spawn_local(async move { - let screen_details = match future.await { - Ok(screen_details) => screen_details.unchecked_into(), + let details = match future.await { + Ok(details) => details.unchecked_into(), Err(error) => unreachable_error( &error, "getting screen details failed even though permission was granted", @@ -439,9 +542,9 @@ impl MonitorHandler { if let Some(runner) = runner.upgrade() { // We drop the event listener handle here, which - // doesn't drop it while we are running it, because + // doesn't drop it during its execution, because // we are in a `spawn_local()` context. - *runner.monitor().state.borrow_mut() = State::Detailed(screen_details); + runner.monitor().upgrade(details); } }); } @@ -449,6 +552,12 @@ impl MonitorHandler { ) } + /// Elevate [`MonitorHandler`] to [`ScreenDetails`]. + fn upgrade(&self, details: ScreenDetails) { + *self.state.borrow_mut() = + State::Detailed(Detailed { details, id_counter: 0, screens: Vec::new() }); + } + pub fn is_extended(&self) -> Option { self.screen.is_extended() } @@ -457,16 +566,19 @@ impl MonitorHandler { matches!(self.state.borrow().deref(), State::Initialize(_)) } + fn handle(&self, detailed: &mut Detailed, screen: ScreenDetailed) -> MonitorHandle { + detailed.handle( + self.main_thread, + self.runner.clone(), + self.window.clone(), + self.engine, + screen, + ) + } + pub fn current_monitor(&self) -> MonitorHandle { - if let State::Detailed(details) = self.state.borrow().deref() { - MonitorHandle::new( - self.main_thread, - Inner::new( - self.window.clone(), - self.engine, - Screen::Detailed(details.current_screen()), - ), - ) + if let State::Detailed(detailed) = self.state.borrow_mut().deref_mut() { + self.handle(detailed, detailed.details.current_screen()) } else { MonitorHandle::new( self.main_thread, @@ -477,106 +589,80 @@ impl MonitorHandler { // Note: We have to return a `Vec` here because the iterator is otherwise not `Send` + `Sync`. pub fn available_monitors(&self) -> Vec { - if let State::Detailed(details) = self.state.borrow().deref() { - details + let mut state = self.state.borrow_mut(); + if let State::Detailed(detailed) = state.deref_mut() { + detailed + .details .screens() .into_iter() - .map(move |screen| { - MonitorHandle::new( - self.main_thread, - Inner::new(self.window.clone(), self.engine, Screen::Detailed(screen)), - ) - }) + .map(move |screen| self.handle(detailed, screen)) .collect() } else { + drop(state); vec![self.current_monitor()] } } pub fn primary_monitor(&self) -> Option { - if let State::Detailed(details) = self.state.borrow().deref() { - details.screens().into_iter().find_map(|screen| { - screen.is_primary().then(|| { - MonitorHandle::new( - self.main_thread, - Inner::new(self.window.clone(), self.engine, Screen::Detailed(screen)), - ) - }) - }) + if let State::Detailed(detailed) = self.state.borrow_mut().deref_mut() { + detailed + .details + .screens() + .into_iter() + .find_map(|screen| screen.is_primary().then(|| self.handle(detailed, screen))) } else { None } } - pub(crate) fn request_detailed_monitor_permission( - &self, - shared: WeakShared, - ) -> MonitorPermissionFuture { + pub(crate) fn request_detailed_monitor_permission(&self) -> MonitorPermissionFuture { let state = self.state.borrow(); - let (notifier, notified) = match state.deref() { + let permission = match state.deref() { State::Unsupported => { return MonitorPermissionFuture::Ready(Some(Err( MonitorPermissionError::Unsupported, ))) }, + // If we are currently initializing, wait for initialization to finish before we do our + // thing. State::Initialize(notified) => { return MonitorPermissionFuture::Initialize { - runner: Dispatcher::new(self.main_thread, (shared, self.window.clone())).0, + runner: Dispatcher::new( + self.main_thread, + (self.runner.clone(), self.window.clone()), + ) + .0, notified: notified.clone(), } }, - State::Permission { permission, .. } => { - match permission.state() { - PermissionState::Granted | PermissionState::Prompt => (), - PermissionState::Denied => { - return MonitorPermissionFuture::Ready(Some(Err( - MonitorPermissionError::Denied, - ))) - }, - _ => { - error!( - "encountered unknown permission state: {}", - permission.state_string() - ); + // If we finished initialization we at least possess `PermissionStatus`. + State::Permission { permission, .. } => permission, + // A request is already in progress. Use that! + State::Upgrade(notified) => return MonitorPermissionFuture::Upgrade(notified.clone()), + State::Detailed { .. } => return MonitorPermissionFuture::Ready(Some(Ok(()))), + }; - return MonitorPermissionFuture::Ready(Some(Err( - MonitorPermissionError::Denied, - ))); - }, - } + match permission.state() { + PermissionState::Granted | PermissionState::Prompt => (), + PermissionState::Denied => { + return MonitorPermissionFuture::Ready(Some(Err(MonitorPermissionError::Denied))) + }, + _ => { + error!("encountered unknown permission state: {}", permission.state_string()); - drop(state); + return MonitorPermissionFuture::Ready(Some(Err(MonitorPermissionError::Denied))); + }, + } - let notifier = Notifier::new(); - let notified = notifier.notified(); - *self.state.borrow_mut() = State::Upgrade(notified.clone()); + drop(state); - (notifier, notified) - }, - // A request is already in progress. - State::Upgrade(notified) => return MonitorPermissionFuture::Upgrade(notified.clone()), - State::Detailed(_) => return MonitorPermissionFuture::Ready(Some(Ok(()))), - }; + // We are ready to explicitly ask the user for permission, lets go! - let future = JsFuture::from(self.window.screen_details()); - wasm_bindgen_futures::spawn_local(async move { - match future.await { - Ok(details) => { - // Notifying `Future`s is not dependant on the lifetime of the runner, because - // they can outlive it. - notifier.notify(Ok(())); + let notifier = Notifier::new(); + let notified = notifier.notified(); + *self.state.borrow_mut() = State::Upgrade(notified.clone()); - if let Some(shared) = shared.upgrade() { - *shared.monitor().state.borrow_mut() = - State::Detailed(details.unchecked_into()) - } - }, - Err(error) => unreachable_error( - &error, - "getting screen details failed even though permission was granted", - ), - } - }); + MonitorPermissionFuture::upgrade_internal(self.runner.clone(), &self.window, notifier); MonitorPermissionFuture::Upgrade(notified) } @@ -587,7 +673,7 @@ impl MonitorHandler { HasMonitorPermissionFuture::Ready(Some(false)) }, State::Initialize(notified) => HasMonitorPermissionFuture::Future(notified.clone()), - State::Detailed(_) => HasMonitorPermissionFuture::Ready(Some(true)), + State::Detailed { .. } => HasMonitorPermissionFuture::Ready(Some(true)), } } @@ -597,7 +683,7 @@ impl MonitorHandler { State::Initialize(_) => { unreachable!("called `has_detailed_monitor_permission()` while initializing") }, - State::Detailed(_) => true, + State::Detailed { .. } => true, } } } @@ -621,34 +707,38 @@ impl MonitorPermissionFuture { unreachable!() }; - runner.dispatch(|(shared, window)| { - let future = JsFuture::from(window.screen_details()); - - if let Some(shared) = shared.upgrade() { - *shared.monitor().state.borrow_mut() = State::Upgrade(notified); + runner.dispatch(|(runner, window)| { + if let Some(runner) = runner.upgrade() { + *runner.monitor().state.borrow_mut() = State::Upgrade(notified); } - let shared = shared.clone(); - wasm_bindgen_futures::spawn_local(async move { - match future.await { - Ok(details) => { - // Notifying `Future`s is not dependant on the lifetime - // of - // the runner, because - // they can outlive it. - notifier.notify(Ok(())); + Self::upgrade_internal(runner.clone(), window, notifier); + }); + } - if let Some(shared) = shared.upgrade() { - *shared.monitor().state.borrow_mut() = - State::Detailed(details.unchecked_into()) - } - }, - Err(error) => unreachable_error( - &error, - "getting screen details failed even though permission was granted", - ), - } - }); + fn upgrade_internal( + runner: WeakShared, + window: &WindowExt, + notifier: Notifier>, + ) { + let future = JsFuture::from(window.screen_details()); + + wasm_bindgen_futures::spawn_local(async move { + match future.await { + Ok(details) => { + // Notifying `Future`s is not dependant on the lifetime of the runner, because + // they can outlive it. + notifier.notify(Ok(())); + + if let Some(runner) = runner.upgrade() { + runner.monitor().upgrade(details.unchecked_into()); + } + }, + Err(error) => unreachable_error( + &error, + "getting screen details failed even though permission was granted", + ), + } }); } } @@ -714,8 +804,10 @@ fn unreachable_error(error: &JsValue, message: &str) -> ! { } } -thread_local! { - static HAS_LOCK_SUPPORT: OnceCell = const { OnceCell::new() }; +static HAS_LOCK_SUPPORT: OnceLock = OnceLock::new(); + +fn has_previous_lock_support() -> Option { + HAS_LOCK_SUPPORT.get().cloned() } pub fn has_screen_details_support(window: &Window) -> bool { @@ -751,7 +843,7 @@ extern "C" { #[wasm_bindgen(method, getter)] fn screens(this: &ScreenDetails) -> Vec; - #[derive(Clone)] + #[derive(Clone, PartialEq)] #[wasm_bindgen(extends = web_sys::Screen)] pub(crate) type ScreenExt; @@ -767,6 +859,7 @@ extern "C" { #[wasm_bindgen(method, getter, js_name = lock)] fn has_lock(this: &ScreenOrientationExt) -> JsValue; + #[derive(PartialEq)] #[wasm_bindgen(extends = ScreenExt)] pub(crate) type ScreenDetailed;