diff --git a/clippy.toml b/clippy.toml index 70fbe707..de970d87 100644 --- a/clippy.toml +++ b/clippy.toml @@ -4,4 +4,6 @@ disallowed-methods = [ { path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" }, { path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" }, + { path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" }, + { path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" }, ] diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 4fd68ade..dd71a325 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -19,7 +19,7 @@ use std::{ rc::{Rc, Weak}, }; use wasm_bindgen::prelude::Closure; -use web_sys::{KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent}; +use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent}; use web_time::{Duration, Instant}; pub struct Shared<T: 'static>(Rc<Execution<T>>); @@ -41,6 +41,7 @@ pub struct Execution<T: 'static> { events: RefCell<VecDeque<EventWrapper<T>>>, id: RefCell<u32>, window: web_sys::Window, + document: Document, all_canvases: RefCell<Vec<(WindowId, Weak<RefCell<backend::Canvas>>)>>, redraw_pending: RefCell<HashSet<WindowId>>, destroy_pending: RefCell<VecDeque<WindowId>>, @@ -141,13 +142,18 @@ impl<T: 'static> Runner<T> { impl<T: 'static> Shared<T> { pub fn new() -> Self { + #[allow(clippy::disallowed_methods)] + let window = web_sys::window().expect("only callable from inside the `Window`"); + #[allow(clippy::disallowed_methods)] + let document = window.document().expect("Failed to obtain document"); + Shared(Rc::new(Execution { runner: RefCell::new(RunnerEnum::Pending), suspended: Cell::new(false), event_loop_recreation: Cell::new(false), events: RefCell::new(VecDeque::new()), - #[allow(clippy::disallowed_methods)] - window: web_sys::window().expect("only callable from inside the `Window`"), + window, + document, id: RefCell::new(0), all_canvases: RefCell::new(Vec::new()), redraw_pending: RefCell::new(HashSet::new()), @@ -168,6 +174,10 @@ impl<T: 'static> Shared<T> { &self.0.window } + pub fn document(&self) -> &Document { + &self.0.document + } + pub fn add_canvas(&self, id: WindowId, canvas: &Rc<RefCell<backend::Canvas>>) { self.0 .all_canvases @@ -394,13 +404,13 @@ impl<T: 'static> Shared<T> { let runner = self.clone(); *self.0.on_visibility_change.borrow_mut() = Some(EventListenerHandle::new( // Safari <14 doesn't support the `visibilitychange` event on `Window`. - &self.window().document().expect("Failed to obtain document"), + self.document(), "visibilitychange", Closure::new(move |_| { if !runner.0.suspended.get() { for (id, canvas) in &*runner.0.all_canvases.borrow() { if let Some(canvas) = canvas.upgrade() { - let is_visible = backend::is_visible(runner.window()); + let is_visible = backend::is_visible(runner.document()); // only fire if: // - not visible and intersects // - not visible and we don't know if it intersects yet diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index faf2403a..72893f06 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -725,7 +725,7 @@ impl<T> EventLoopWindowTarget<T> { let runner = self.runner.clone(); canvas.on_intersection(move |is_intersecting| { // only fire if visible while skipping the first event if it's intersecting - if backend::is_visible(runner.window()) + if backend::is_visible(runner.document()) && !(is_intersecting && canvas_clone.borrow().is_intersecting.is_none()) { runner.send_event(Event::WindowEvent { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 8ef4f38f..14f842fc 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -21,7 +21,9 @@ 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, WheelEvent}; +use web_sys::{ + CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent, +}; #[allow(dead_code)] pub struct Canvas { @@ -44,8 +46,10 @@ pub struct Canvas { pub struct Common { pub window: web_sys::Window, + pub document: Document, /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. pub raw: HtmlCanvasElement, + style: CssStyleDeclaration, old_size: Rc<Cell<PhysicalSize<u32>>>, current_size: Rc<Cell<PhysicalSize<u32>>>, wants_fullscreen: Rc<RefCell<bool>>, @@ -55,21 +59,16 @@ impl Canvas { pub fn create( id: WindowId, window: web_sys::Window, + document: Document, attr: &WindowAttributes, platform_attr: PlatformSpecificWindowBuilderAttributes, ) -> Result<Self, RootOE> { let canvas = match platform_attr.canvas { Some(canvas) => canvas, - None => { - let document = window - .document() - .ok_or_else(|| os_error!(OsError("Failed to obtain document".to_owned())))?; - - document - .create_element("canvas") - .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? - .unchecked_into() - } + None => document + .create_element("canvas") + .map_err(|_| os_error!(OsError("Failed to create canvas element".to_owned())))? + .unchecked_into(), }; // A tabindex is needed in order to capture local keyboard events. @@ -83,15 +82,24 @@ impl Canvas { .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; } + #[allow(clippy::disallowed_methods)] + let style = window + .get_computed_style(&canvas) + .expect("Failed to obtain computed style") + // this can't fail: we aren't using a pseudo-element + .expect("Invalid pseudo-element"); + if let Some(size) = attr.inner_size { let size = size.to_logical(super::scale_factor(&window)); - super::set_canvas_size(&window, &canvas, size); + super::set_canvas_size(&document, &canvas, &style, size); } Ok(Canvas { common: Common { window, + document, raw: canvas, + style, old_size: Rc::default(), current_size: Rc::default(), wants_fullscreen: Rc::new(RefCell::new(false)), @@ -117,12 +125,7 @@ impl Canvas { if lock { self.raw().request_pointer_lock(); } else { - let document = self - .common - .window - .document() - .ok_or_else(|| os_error!(OsError("Failed to obtain document".to_owned())))?; - document.exit_pointer_lock(); + self.common.document.exit_pointer_lock(); } Ok(()) } @@ -141,50 +144,58 @@ impl Canvas { y: bounds.y(), }; - let document = self.window().document().expect("Failed to obtain document"); - - if document.contains(Some(self.raw())) { - let style = self - .window() - .get_computed_style(self.raw()) - .expect("Failed to obtain computed style") - // this can't fail: we aren't using a pseudo-element - .expect("Invalid pseudo-element"); - if style.get_property_value("display").unwrap() != "none" { - position.x += super::style_size_property(&style, "border-left-width") - + super::style_size_property(&style, "padding-left"); - position.y += super::style_size_property(&style, "border-top-width") - + super::style_size_property(&style, "padding-top"); - } + if self.document().contains(Some(self.raw())) + && self.style().get_property_value("display").unwrap() != "none" + { + position.x += super::style_size_property(self.style(), "border-left-width") + + super::style_size_property(self.style(), "padding-left"); + position.y += super::style_size_property(self.style(), "border-top-width") + + super::style_size_property(self.style(), "padding-top"); } position } + #[inline] pub fn old_size(&self) -> PhysicalSize<u32> { self.common.old_size.get() } + #[inline] pub fn inner_size(&self) -> PhysicalSize<u32> { self.common.current_size.get() } + #[inline] pub fn set_old_size(&self, size: PhysicalSize<u32>) { self.common.old_size.set(size) } + #[inline] pub fn set_current_size(&self, size: PhysicalSize<u32>) { self.common.current_size.set(size) } + #[inline] pub fn window(&self) -> &web_sys::Window { &self.common.window } + #[inline] + pub fn document(&self) -> &Document { + &self.common.document + } + + #[inline] pub fn raw(&self) -> &HtmlCanvasElement { &self.common.raw } + #[inline] + pub fn style(&self) -> &CssStyleDeclaration { + &self.common.style + } + pub fn on_touch_start(&mut self, prevent_default: bool) { self.on_touch_start = Some(self.common.add_event("touchstart", move |event: Event| { if prevent_default { @@ -382,7 +393,9 @@ impl Canvas { { self.on_resize_scale = Some(ResizeScaleHandle::new( self.window().clone(), + self.document().clone(), self.raw().clone(), + self.style().clone(), scale_handler, size_handler, )); @@ -425,7 +438,7 @@ impl Canvas { // Then we resize the canvas to the new size, a new // `Resized` event will be sent by the `ResizeObserver`: let new_size = new_size.to_logical(scale); - super::set_canvas_size(self.window(), self.raw(), new_size); + super::set_canvas_size(self.document(), self.raw(), self.style(), new_size); // Set the size might not trigger the event because the calculation is inaccurate. self.on_resize_scale @@ -527,6 +540,6 @@ impl Common { } pub fn is_fullscreen(&self) -> bool { - super::is_fullscreen(&self.window, &self.raw) + super::is_fullscreen(&self.document, &self.raw) } } diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 273c57ca..a45a65ac 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -18,16 +18,14 @@ use crate::platform::web::WindowExtWebSys; use crate::window::Window; use wasm_bindgen::closure::Closure; use web_sys::{ - CssStyleDeclaration, Element, HtmlCanvasElement, PageTransitionEvent, VisibilityState, + CssStyleDeclaration, Document, Element, HtmlCanvasElement, PageTransitionEvent, VisibilityState, }; pub fn throw(msg: &str) { wasm_bindgen::throw_str(msg); } -pub fn exit_fullscreen(window: &web_sys::Window) { - let document = window.document().expect("Failed to obtain document"); - +pub fn exit_fullscreen(document: &Document) { document.exit_fullscreen(); } @@ -69,35 +67,28 @@ pub fn scale_factor(window: &web_sys::Window) -> f64 { } pub fn set_canvas_size( - window: &web_sys::Window, + document: &Document, raw: &HtmlCanvasElement, + style: &CssStyleDeclaration, mut new_size: LogicalSize<f64>, ) { - let document = window.document().expect("Failed to obtain document"); - if !document.contains(Some(raw)) { return; } - let style = window - .get_computed_style(raw) - .expect("Failed to obtain computed style") - // this can't fail: we aren't using a pseudo-element - .expect("Invalid pseudo-element"); - if style.get_property_value("display").unwrap() == "none" { return; } if style.get_property_value("box-sizing").unwrap() == "border-box" { - new_size.width += style_size_property(&style, "border-left-width") - + style_size_property(&style, "border-right-width") - + style_size_property(&style, "padding-left") - + style_size_property(&style, "padding-right"); - new_size.height += style_size_property(&style, "border-top-width") - + style_size_property(&style, "border-bottom-width") - + style_size_property(&style, "padding-top") - + style_size_property(&style, "padding-bottom"); + new_size.width += style_size_property(style, "border-left-width") + + style_size_property(style, "border-right-width") + + style_size_property(style, "padding-left") + + style_size_property(style, "padding-right"); + new_size.height += style_size_property(style, "border-top-width") + + style_size_property(style, "border-bottom-width") + + style_size_property(style, "padding-top") + + style_size_property(style, "padding-bottom"); } set_canvas_style_property(raw, "width", &format!("{}px", new_size.width)); @@ -123,9 +114,7 @@ pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: .unwrap_or_else(|err| panic!("error: {err:?}\nFailed to set {property}")) } -pub fn is_fullscreen(window: &web_sys::Window, canvas: &HtmlCanvasElement) -> bool { - let document = window.document().expect("Failed to obtain document"); - +pub fn is_fullscreen(document: &Document, canvas: &HtmlCanvasElement) -> bool { match document.fullscreen_element() { Some(elem) => { let canvas: &Element = canvas; @@ -143,8 +132,7 @@ pub fn is_dark_mode(window: &web_sys::Window) -> Option<bool> { .map(|media| media.matches()) } -pub fn is_visible(window: &web_sys::Window) -> bool { - let document = window.document().expect("Failed to obtain document"); +pub fn is_visible(document: &Document) -> bool { document.visibility_state() == VisibilityState::Visible } diff --git a/src/platform_impl/web/web_sys/resize_scaling.rs b/src/platform_impl/web/web_sys/resize_scaling.rs index d80f94a8..8ecd1bd5 100644 --- a/src/platform_impl/web/web_sys/resize_scaling.rs +++ b/src/platform_impl/web/web_sys/resize_scaling.rs @@ -3,8 +3,9 @@ use once_cell::unsync::Lazy; use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::{ - HtmlCanvasElement, MediaQueryList, ResizeObserver, ResizeObserverBoxOptions, - ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, Window, + CssStyleDeclaration, Document, HtmlCanvasElement, MediaQueryList, ResizeObserver, + ResizeObserverBoxOptions, ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, + Window, }; use crate::dpi::{LogicalSize, PhysicalSize}; @@ -20,7 +21,9 @@ pub struct ResizeScaleHandle(Rc<RefCell<ResizeScaleInternal>>); impl ResizeScaleHandle { pub(crate) fn new<S, R>( window: Window, + document: Document, canvas: HtmlCanvasElement, + style: CssStyleDeclaration, scale_handler: S, resize_handler: R, ) -> Self @@ -30,7 +33,9 @@ impl ResizeScaleHandle { { Self(ResizeScaleInternal::new( window, + document, canvas, + style, scale_handler, resize_handler, )) @@ -45,7 +50,9 @@ impl ResizeScaleHandle { /// changes of the `devicePixelRatio`. struct ResizeScaleInternal { window: Window, + document: Document, canvas: HtmlCanvasElement, + style: CssStyleDeclaration, mql: MediaQueryListHandle, observer: ResizeObserver, _observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>, @@ -57,7 +64,9 @@ struct ResizeScaleInternal { impl ResizeScaleInternal { fn new<S, R>( window: Window, + document: Document, canvas: HtmlCanvasElement, + style: CssStyleDeclaration, scale_handler: S, resize_handler: R, ) -> Rc<RefCell<Self>> @@ -80,7 +89,7 @@ impl ResizeScaleInternal { if let Some(rc_self) = weak_self.upgrade() { let mut this = rc_self.borrow_mut(); - let size = Self::process_entry(&this.window, &this.canvas, entries); + let size = this.process_entry(entries); if this.notify_scale.replace(false) { let scale = backend::scale_factor(&this.window); @@ -94,7 +103,9 @@ impl ResizeScaleInternal { RefCell::new(Self { window, + document, canvas, + style, mql, observer, _observer_closure: observer_closure, @@ -142,17 +153,8 @@ impl ResizeScaleInternal { } fn notify(&mut self) { - let style = self - .window - .get_computed_style(&self.canvas) - .expect("Failed to obtain computed style") - // this can't fail: we aren't using a pseudo-element - .expect("Invalid pseudo-element"); - - let document = self.window.document().expect("Failed to obtain document"); - - if !document.contains(Some(&self.canvas)) - || style.get_property_value("display").unwrap() == "none" + if !self.document.contains(Some(&self.canvas)) + || self.style.get_property_value("display").unwrap() == "none" { let size = PhysicalSize::new(0, 0); @@ -175,19 +177,19 @@ impl ResizeScaleInternal { } let mut size = LogicalSize::new( - backend::style_size_property(&style, "width"), - backend::style_size_property(&style, "height"), + backend::style_size_property(&self.style, "width"), + backend::style_size_property(&self.style, "height"), ); - if style.get_property_value("box-sizing").unwrap() == "border-box" { - size.width -= backend::style_size_property(&style, "border-left-width") - + backend::style_size_property(&style, "border-right-width") - + backend::style_size_property(&style, "padding-left") - + backend::style_size_property(&style, "padding-right"); - size.height -= backend::style_size_property(&style, "border-top-width") - + backend::style_size_property(&style, "border-bottom-width") - + backend::style_size_property(&style, "padding-top") - + backend::style_size_property(&style, "padding-bottom"); + if self.style.get_property_value("box-sizing").unwrap() == "border-box" { + size.width -= backend::style_size_property(&self.style, "border-left-width") + + backend::style_size_property(&self.style, "border-right-width") + + backend::style_size_property(&self.style, "padding-left") + + backend::style_size_property(&self.style, "padding-right"); + size.height -= backend::style_size_property(&self.style, "border-top-width") + + backend::style_size_property(&self.style, "border-bottom-width") + + backend::style_size_property(&self.style, "padding-top") + + backend::style_size_property(&self.style, "padding-bottom"); } let size = size.to_physical(backend::scale_factor(&self.window)); @@ -229,11 +231,7 @@ impl ResizeScaleInternal { this.notify(); } - fn process_entry( - window: &Window, - canvas: &HtmlCanvasElement, - entries: Array, - ) -> PhysicalSize<u32> { + fn process_entry(&self, entries: Array) -> PhysicalSize<u32> { let entry: ResizeObserverEntry = entries.get(0).unchecked_into(); // Safari doesn't support `devicePixelContentBoxSize` @@ -241,7 +239,7 @@ impl ResizeScaleInternal { let rect = entry.content_rect(); return LogicalSize::new(rect.width(), rect.height()) - .to_physical(backend::scale_factor(window)); + .to_physical(backend::scale_factor(&self.window)); } let entry: ResizeObserverSize = entry @@ -249,13 +247,8 @@ impl ResizeScaleInternal { .get(0) .unchecked_into(); - let style = window - .get_computed_style(canvas) - .expect("Failed to get computed style of canvas") - // this can only be empty if we provided an invalid `pseudoElt` - .expect("`getComputedStyle` can not be empty"); - - let writing_mode = style + let writing_mode = self + .style .get_property_value("writing-mode") .expect("`writing-mode` is a valid CSS property"); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index eb7ad858..805126ef 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -7,7 +7,7 @@ use crate::window::{ }; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle}; -use web_sys::HtmlCanvasElement; +use web_sys::{CssStyleDeclaration, Document, HtmlCanvasElement}; use super::r#async::Dispatcher; use super::{backend, monitor::MonitorHandle, EventLoopWindowTarget, Fullscreen}; @@ -27,6 +27,8 @@ pub struct Window { pub struct Inner { pub window: web_sys::Window, + document: Document, + style: CssStyleDeclaration, canvas: Rc<RefCell<backend::Canvas>>, previous_pointer: RefCell<&'static str>, register_redraw_request: Box<dyn Fn()>, @@ -46,7 +48,10 @@ impl Window { let prevent_default = platform_attr.prevent_default; let window = target.runner.window(); - let canvas = backend::Canvas::create(id, window.clone(), &attr, platform_attr)?; + let document = target.runner.document(); + let canvas = + backend::Canvas::create(id, window.clone(), document.clone(), &attr, platform_attr)?; + let style = canvas.style().clone(); let canvas = Rc::new(RefCell::new(canvas)); let register_redraw_request = Box::new(move || runner.request_redraw(RootWI(id))); @@ -62,6 +67,8 @@ impl Window { has_focus, inner: Dispatcher::new(Inner { window: window.clone(), + document: document.clone(), + style, canvas, previous_pointer: RefCell::new("auto"), register_redraw_request, @@ -130,24 +137,16 @@ impl Window { let mut position = position.to_logical::<f64>(inner.scale_factor()); let canvas = inner.canvas.borrow(); - let document = inner.window.document().expect("Failed to obtain document"); - if document.contains(Some(canvas.raw())) { - let style = inner - .window - .get_computed_style(canvas.raw()) - .expect("Failed to obtain computed style") - // this can't fail: we aren't using a pseudo-element - .expect("Invalid pseudo-element"); - - if style.get_property_value("display").unwrap() != "none" { - position.x -= backend::style_size_property(&style, "margin-left") - + backend::style_size_property(&style, "border-left-width") - + backend::style_size_property(&style, "padding-left"); - position.y -= backend::style_size_property(&style, "margin-top") - + backend::style_size_property(&style, "border-top-width") - + backend::style_size_property(&style, "padding-top"); - } + if inner.document.contains(Some(canvas.raw())) + && inner.style.get_property_value("display").unwrap() != "none" + { + position.x -= backend::style_size_property(&inner.style, "margin-left") + + backend::style_size_property(&inner.style, "border-left-width") + + backend::style_size_property(&inner.style, "padding-left"); + position.y -= backend::style_size_property(&inner.style, "margin-top") + + backend::style_size_property(&inner.style, "border-top-width") + + backend::style_size_property(&inner.style, "padding-top"); } canvas.set_attribute("position", "fixed"); @@ -172,7 +171,7 @@ impl Window { self.inner.dispatch(move |inner| { let size = size.to_logical(inner.scale_factor()); let canvas = inner.canvas.borrow(); - backend::set_canvas_size(canvas.window(), canvas.raw(), size); + backend::set_canvas_size(canvas.document(), canvas.raw(), canvas.style(), size); }); None @@ -324,7 +323,7 @@ impl Window { if fullscreen.is_some() { inner.canvas.borrow().request_fullscreen(); } else if inner.canvas.borrow().is_fullscreen() { - backend::exit_fullscreen(&inner.window); + backend::exit_fullscreen(&inner.document); } }); }