From e09c43a34ea5ef08dc7b4362e34e11abc45d894c Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Mon, 5 Jun 2023 01:23:48 +0200 Subject: [PATCH] Make media queries more robust --- CHANGELOG.md | 1 + Cargo.toml | 1 - src/platform_impl/web/web_sys/canvas.rs | 16 ++--- .../web/web_sys/media_query_handle.rs | 52 ++++++-------- src/platform_impl/web/web_sys/scaling.rs | 70 ++++++++++--------- 5 files changed, 66 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 486286c779..dd286764f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ And please only add new entries to the top of this list, right below the `# Unre - **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`. - On Web, use the correct canvas size when calculating the new size during scale factor change, instead of using the output bitmap size. +- On Web, scale factor and dark mode detection are now more robust. # 0.28.6 diff --git a/Cargo.toml b/Cargo.toml index b13639da04..11245bbd6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,7 +145,6 @@ features = [ 'HtmlElement', 'KeyboardEvent', 'MediaQueryList', - 'MediaQueryListEvent', 'Node', 'PointerEvent', 'Window', diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 4b5dde990c..25bc63a764 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -17,9 +17,7 @@ use smol_str::SmolStr; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{closure::Closure, JsCast, JsValue}; use wasm_bindgen_futures::JsFuture; -use web_sys::{ - Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, MediaQueryListEvent, WheelEvent, -}; +use web_sys::{Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent}; #[allow(dead_code)] pub struct Canvas { @@ -343,13 +341,11 @@ impl Canvas { where F: 'static + FnMut(bool), { - let closure = - Closure::wrap( - Box::new(move |event: MediaQueryListEvent| handler(event.matches())) - as Box, - ); - self.on_dark_mode = - MediaQueryListHandle::new(&self.common.window, "(prefers-color-scheme: dark)", closure); + self.on_dark_mode = Some(MediaQueryListHandle::new( + &self.common.window, + "(prefers-color-scheme: dark)", + move |mql| handler(mql.matches()), + )); } pub fn request_fullscreen(&self) { diff --git a/src/platform_impl/web/web_sys/media_query_handle.rs b/src/platform_impl/web/web_sys/media_query_handle.rs index a09feebd02..ad78ffd6c3 100644 --- a/src/platform_impl/web/web_sys/media_query_handle.rs +++ b/src/platform_impl/web/web_sys/media_query_handle.rs @@ -1,54 +1,46 @@ use wasm_bindgen::{prelude::Closure, JsCast}; -use web_sys::{MediaQueryList, MediaQueryListEvent}; +use web_sys::MediaQueryList; pub(super) struct MediaQueryListHandle { mql: MediaQueryList, - listener: Option>, + closure: Closure, } impl MediaQueryListHandle { - pub fn new( - window: &web_sys::Window, - media_query: &str, - listener: Closure, - ) -> Option { + pub fn new(window: &web_sys::Window, media_query: &str, mut listener: F) -> Self + where + F: 'static + FnMut(&MediaQueryList), + { let mql = window .match_media(media_query) - .ok() - .flatten() - .and_then(|mql| { - mql.add_listener_with_opt_callback(Some(listener.as_ref().unchecked_ref())) - .map(|_| mql) - .ok() - }); - mql.map(|mql| Self { - mql, - listener: Some(listener), - }) + .expect("Failed to parse media query") + .expect("Found empty media query"); + + let closure = Closure::new({ + let mql = mql.clone(); + move || listener(&mql) + }); + // TODO: Replace obsolete `addListener()` with `addEventListener()` and use + // `MediaQueryListEvent` instead of cloning the `MediaQueryList`. + // Requires Safari v14. + mql.add_listener_with_opt_callback(Some(closure.as_ref().unchecked_ref())) + .expect("Invalid listener"); + + Self { mql, closure } } pub fn mql(&self) -> &MediaQueryList { &self.mql } - - /// Removes the listener and returns the original listener closure, which - /// can be reused. - pub fn remove(mut self) -> Closure { - let listener = self.listener.take().unwrap_or_else(|| unreachable!()); - remove_listener(&self.mql, &listener); - listener - } } impl Drop for MediaQueryListHandle { fn drop(&mut self) { - if let Some(listener) = self.listener.take() { - remove_listener(&self.mql, &listener); - } + remove_listener(&self.mql, &self.closure); } } -fn remove_listener(mql: &MediaQueryList, listener: &Closure) { +fn remove_listener(mql: &MediaQueryList, listener: &Closure) { mql.remove_listener_with_opt_callback(Some(listener.as_ref().unchecked_ref())) .unwrap_or_else(|e| { web_sys::console::error_2(&"Error removing media query listener".into(), &e) diff --git a/src/platform_impl/web/web_sys/scaling.rs b/src/platform_impl/web/web_sys/scaling.rs index dabe9780b0..0b2fe68480 100644 --- a/src/platform_impl/web/web_sys/scaling.rs +++ b/src/platform_impl/web/web_sys/scaling.rs @@ -1,9 +1,9 @@ +use web_sys::MediaQueryList; + use super::super::ScaleChangeArgs; use super::media_query_handle::MediaQueryListHandle; use std::{cell::RefCell, rc::Rc}; -use wasm_bindgen::prelude::Closure; -use web_sys::MediaQueryListEvent; pub struct ScaleChangeDetector(Rc>); @@ -39,53 +39,57 @@ impl ScaleChangeDetectorInternal { })); let weak_self = Rc::downgrade(&new_self); - let closure = Closure::wrap(Box::new(move |event: MediaQueryListEvent| { + let mql = Self::create_mql(&window, move |mql| { if let Some(rc_self) = weak_self.upgrade() { - rc_self.borrow_mut().handler(event); + Self::handler(rc_self, mql); } - }) as Box); - - let mql = Self::create_mql(&window, closure); + }); { let mut borrowed_self = new_self.borrow_mut(); - borrowed_self.mql = mql; + borrowed_self.mql = Some(mql); } new_self } - fn create_mql( - window: &web_sys::Window, - closure: Closure, - ) -> Option { + fn create_mql(window: &web_sys::Window, closure: F) -> MediaQueryListHandle + where + F: 'static + FnMut(&MediaQueryList), + { let current_scale = super::scale_factor(window); - // This media query initially matches the current `devicePixelRatio`. - // We add 0.0001 to the lower and upper bounds such that it won't fail - // due to floating point precision limitations. + // TODO: Remove `-webkit-device-pixel-ratio`. Requires Safari v16. let media_query = format!( - "(min-resolution: {min_scale:.4}dppx) and (max-resolution: {max_scale:.4}dppx), - (-webkit-min-device-pixel-ratio: {min_scale:.4}) and (-webkit-max-device-pixel-ratio: {max_scale:.4})", - min_scale = current_scale - 0.0001, max_scale= current_scale + 0.0001, + "(resolution: {current_scale}dppx), + (-webkit-device-pixel-ratio: {current_scale})", ); let mql = MediaQueryListHandle::new(window, &media_query, closure); - if let Some(mql) = &mql { - assert!(mql.mql().matches()); - } + assert!( + mql.mql().matches(), + "created media query doesn't match, {current_scale} != {}", + super::scale_factor(window,) + ); mql } - fn handler(&mut self, _event: MediaQueryListEvent) { - let mql = self - .mql - .take() - .expect("DevicePixelRatioChangeDetector::mql should not be None"); - let closure = mql.remove(); - let new_scale = super::scale_factor(&self.window); - (self.callback)(ScaleChangeArgs { - old_scale: self.last_scale, + fn handler(this: Rc>, mql: &MediaQueryList) { + let weak_self = Rc::downgrade(&this); + let mut this = this.borrow_mut(); + let old_scale = this.last_scale; + let new_scale = super::scale_factor(&this.window); + (this.callback)(ScaleChangeArgs { + old_scale, new_scale, }); - let new_mql = Self::create_mql(&self.window, closure); - self.mql = new_mql; - self.last_scale = new_scale; + + // If this matches, then the scale factor is back to it's + // old value again, so we won't need to update the query. + if !mql.matches() { + let new_mql = Self::create_mql(&this.window, move |mql| { + if let Some(rc_self) = weak_self.upgrade() { + Self::handler(rc_self, mql); + } + }); + this.mql = Some(new_mql); + this.last_scale = new_scale; + } } }