On Web, cache commonly used values (#2947)

This commit is contained in:
daxpedda 2023-07-11 00:11:06 +02:00 committed by GitHub
parent c4d70d75c1
commit af26f01b95
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 131 additions and 126 deletions

View file

@ -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" },
]

View file

@ -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

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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");

View file

@ -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);
}
});
}