Web: forbid additional functions in favor of caching them (#3219)

This commit is contained in:
daxpedda 2023-11-10 22:46:51 +01:00 committed by GitHub
parent 21701a33de
commit eab982c402
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 77 deletions

View file

@ -6,6 +6,7 @@ disallowed-methods = [
{ path = "web_sys::HtmlCanvasElement::set_height", 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::document", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" }, { path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" }, { path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },

View file

@ -49,12 +49,18 @@ pub struct Common {
pub document: Document, pub document: Document,
/// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained. /// Note: resizing the HTMLCanvasElement should go through `backend::set_canvas_size` to ensure the DPI factor is maintained.
pub raw: HtmlCanvasElement, pub raw: HtmlCanvasElement,
style: CssStyleDeclaration, style: Style,
old_size: Rc<Cell<PhysicalSize<u32>>>, old_size: Rc<Cell<PhysicalSize<u32>>>,
current_size: Rc<Cell<PhysicalSize<u32>>>, current_size: Rc<Cell<PhysicalSize<u32>>>,
fullscreen_handler: Rc<FullscreenHandler>, fullscreen_handler: Rc<FullscreenHandler>,
} }
#[derive(Clone)]
pub struct Style {
read: CssStyleDeclaration,
write: CssStyleDeclaration,
}
impl Canvas { impl Canvas {
pub fn create( pub fn create(
id: WindowId, id: WindowId,
@ -90,12 +96,7 @@ impl Canvas {
.map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?; .map_err(|_| os_error!(OsError("Failed to set a tabindex".to_owned())))?;
} }
#[allow(clippy::disallowed_methods)] let style = Style::new(&window, &canvas);
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");
let common = Common { let common = Common {
window: window.clone(), window: window.clone(),
@ -178,9 +179,7 @@ impl Canvas {
y: bounds.y(), y: bounds.y(),
}; };
if self.document().contains(Some(self.raw())) if self.document().contains(Some(self.raw())) && self.style().get("display") != "none" {
&& self.style().get_property_value("display").unwrap() != "none"
{
position.x += super::style_size_property(self.style(), "border-left-width") position.x += super::style_size_property(self.style(), "border-left-width")
+ super::style_size_property(self.style(), "padding-left"); + super::style_size_property(self.style(), "padding-left");
position.y += super::style_size_property(self.style(), "border-top-width") position.y += super::style_size_property(self.style(), "border-top-width")
@ -226,7 +225,7 @@ impl Canvas {
} }
#[inline] #[inline]
pub fn style(&self) -> &CssStyleDeclaration { pub fn style(&self) -> &Style {
&self.common.style &self.common.style
} }
@ -563,3 +562,37 @@ impl Common {
}) })
} }
} }
impl Style {
fn new(window: &web_sys::Window, canvas: &HtmlCanvasElement) -> Self {
#[allow(clippy::disallowed_methods)]
let read = 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");
#[allow(clippy::disallowed_methods)]
let write = canvas.style();
Self { read, write }
}
pub(crate) fn get(&self, property: &str) -> String {
self.read
.get_property_value(property)
.expect("Invalid property")
}
pub(crate) fn remove(&self, property: &str) {
self.write
.remove_property(property)
.expect("Property is read only");
}
pub(crate) fn set(&self, property: &str, value: &str) {
self.write
.set_property(property, value)
.expect("Property is read only");
}
}

View file

@ -10,6 +10,7 @@ mod resize_scaling;
mod schedule; mod schedule;
pub use self::canvas::Canvas; pub use self::canvas::Canvas;
use self::canvas::Style;
pub use self::event::ButtonsState; pub use self::event::ButtonsState;
pub use self::event_handle::EventListenerHandle; pub use self::event_handle::EventListenerHandle;
pub use self::resize_scaling::ResizeScaleHandle; pub use self::resize_scaling::ResizeScaleHandle;
@ -17,9 +18,7 @@ pub use self::schedule::Schedule;
use crate::dpi::{LogicalPosition, LogicalSize}; use crate::dpi::{LogicalPosition, LogicalSize};
use wasm_bindgen::closure::Closure; use wasm_bindgen::closure::Closure;
use web_sys::{ use web_sys::{Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState};
CssStyleDeclaration, Document, HtmlCanvasElement, PageTransitionEvent, VisibilityState,
};
pub fn throw(msg: &str) { pub fn throw(msg: &str) {
wasm_bindgen::throw_str(msg); wasm_bindgen::throw_str(msg);
@ -51,8 +50,8 @@ pub fn scale_factor(window: &web_sys::Window) -> f64 {
window.device_pixel_ratio() window.device_pixel_ratio()
} }
fn fix_canvas_size(style: &CssStyleDeclaration, mut size: LogicalSize<f64>) -> LogicalSize<f64> { fn fix_canvas_size(style: &Style, mut size: LogicalSize<f64>) -> LogicalSize<f64> {
if style.get_property_value("box-sizing").unwrap() == "border-box" { if style.get("box-sizing") == "border-box" {
size.width += style_size_property(style, "border-left-width") size.width += style_size_property(style, "border-left-width")
+ style_size_property(style, "border-right-width") + style_size_property(style, "border-right-width")
+ style_size_property(style, "padding-left") + style_size_property(style, "padding-left")
@ -69,76 +68,68 @@ fn fix_canvas_size(style: &CssStyleDeclaration, mut size: LogicalSize<f64>) -> L
pub fn set_canvas_size( pub fn set_canvas_size(
document: &Document, document: &Document,
raw: &HtmlCanvasElement, raw: &HtmlCanvasElement,
style: &CssStyleDeclaration, style: &Style,
new_size: LogicalSize<f64>, new_size: LogicalSize<f64>,
) { ) {
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" { if !document.contains(Some(raw)) || style.get("display") == "none" {
return; return;
} }
let new_size = fix_canvas_size(style, new_size); let new_size = fix_canvas_size(style, new_size);
set_canvas_style_property(raw, "width", &format!("{}px", new_size.width)); style.set("width", &format!("{}px", new_size.width));
set_canvas_style_property(raw, "height", &format!("{}px", new_size.height)); style.set("height", &format!("{}px", new_size.height));
} }
pub fn set_canvas_min_size( pub fn set_canvas_min_size(
document: &Document, document: &Document,
raw: &HtmlCanvasElement, raw: &HtmlCanvasElement,
style: &CssStyleDeclaration, style: &Style,
dimensions: Option<LogicalSize<f64>>, dimensions: Option<LogicalSize<f64>>,
) { ) {
if let Some(dimensions) = dimensions { if let Some(dimensions) = dimensions {
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" { if !document.contains(Some(raw)) || style.get("display") == "none" {
return; return;
} }
let new_size = fix_canvas_size(style, dimensions); let new_size = fix_canvas_size(style, dimensions);
set_canvas_style_property(raw, "min-width", &format!("{}px", new_size.width)); style.set("min-width", &format!("{}px", new_size.width));
set_canvas_style_property(raw, "min-height", &format!("{}px", new_size.height)); style.set("min-height", &format!("{}px", new_size.height));
} else { } else {
style style.remove("min-width");
.remove_property("min-width") style.remove("min-height");
.expect("Property is read only");
style
.remove_property("min-height")
.expect("Property is read only");
} }
} }
pub fn set_canvas_max_size( pub fn set_canvas_max_size(
document: &Document, document: &Document,
raw: &HtmlCanvasElement, raw: &HtmlCanvasElement,
style: &CssStyleDeclaration, style: &Style,
dimensions: Option<LogicalSize<f64>>, dimensions: Option<LogicalSize<f64>>,
) { ) {
if let Some(dimensions) = dimensions { if let Some(dimensions) = dimensions {
if !document.contains(Some(raw)) || style.get_property_value("display").unwrap() == "none" { if !document.contains(Some(raw)) || style.get("display") == "none" {
return; return;
} }
let new_size = fix_canvas_size(style, dimensions); let new_size = fix_canvas_size(style, dimensions);
set_canvas_style_property(raw, "max-width", &format!("{}px", new_size.width)); style.set("max-width", &format!("{}px", new_size.width));
set_canvas_style_property(raw, "max-height", &format!("{}px", new_size.height)); style.set("max-height", &format!("{}px", new_size.height));
} else { } else {
style style.remove("max-width");
.remove_property("max-width") style.remove("max-height");
.expect("Property is read only");
style
.remove_property("max-height")
.expect("Property is read only");
} }
} }
pub fn set_canvas_position( pub fn set_canvas_position(
document: &Document, document: &Document,
raw: &HtmlCanvasElement, raw: &HtmlCanvasElement,
style: &CssStyleDeclaration, style: &Style,
mut position: LogicalPosition<f64>, mut position: LogicalPosition<f64>,
) { ) {
if document.contains(Some(raw)) && style.get_property_value("display").unwrap() != "none" { if document.contains(Some(raw)) && style.get("display") != "none" {
position.x -= style_size_property(style, "margin-left") position.x -= style_size_property(style, "margin-left")
+ style_size_property(style, "border-left-width") + style_size_property(style, "border-left-width")
+ style_size_property(style, "padding-left"); + style_size_property(style, "padding-left");
@ -147,30 +138,21 @@ pub fn set_canvas_position(
+ style_size_property(style, "padding-top"); + style_size_property(style, "padding-top");
} }
set_canvas_style_property(raw, "position", "fixed"); style.set("position", "fixed");
set_canvas_style_property(raw, "left", &format!("{}px", position.x)); style.set("left", &format!("{}px", position.x));
set_canvas_style_property(raw, "top", &format!("{}px", position.y)); style.set("top", &format!("{}px", position.y));
} }
/// This function will panic if the element is not inserted in the DOM /// This function will panic if the element is not inserted in the DOM
/// or is not a CSS property that represents a size in pixel. /// or is not a CSS property that represents a size in pixel.
pub fn style_size_property(style: &CssStyleDeclaration, property: &str) -> f64 { pub fn style_size_property(style: &Style, property: &str) -> f64 {
let prop = style let prop = style.get(property);
.get_property_value(property)
.expect("Found invalid property");
prop.strip_suffix("px") prop.strip_suffix("px")
.expect("Element was not inserted into the DOM or is not a size in pixel") .expect("Element was not inserted into the DOM or is not a size in pixel")
.parse() .parse()
.expect("CSS property is not a size in pixel") .expect("CSS property is not a size in pixel")
} }
pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: &str) {
let style = raw.style();
style
.set_property(property, value)
.unwrap_or_else(|err| panic!("error: {err:?}\nFailed to set {property}"))
}
pub fn is_dark_mode(window: &web_sys::Window) -> Option<bool> { pub fn is_dark_mode(window: &web_sys::Window) -> Option<bool> {
window window
.match_media("(prefers-color-scheme: dark)") .match_media("(prefers-color-scheme: dark)")

View file

@ -2,14 +2,14 @@ use js_sys::{Array, Object};
use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen::{JsCast, JsValue};
use web_sys::{ use web_sys::{
CssStyleDeclaration, Document, HtmlCanvasElement, MediaQueryList, ResizeObserver, Document, HtmlCanvasElement, MediaQueryList, ResizeObserver, ResizeObserverBoxOptions,
ResizeObserverBoxOptions, ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, ResizeObserverEntry, ResizeObserverOptions, ResizeObserverSize, Window,
Window,
}; };
use crate::dpi::{LogicalSize, PhysicalSize}; use crate::dpi::{LogicalSize, PhysicalSize};
use super::super::backend; use super::super::backend;
use super::canvas::Style;
use super::media_query_handle::MediaQueryListHandle; use super::media_query_handle::MediaQueryListHandle;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
@ -22,7 +22,7 @@ impl ResizeScaleHandle {
window: Window, window: Window,
document: Document, document: Document,
canvas: HtmlCanvasElement, canvas: HtmlCanvasElement,
style: CssStyleDeclaration, style: Style,
scale_handler: S, scale_handler: S,
resize_handler: R, resize_handler: R,
) -> Self ) -> Self
@ -51,7 +51,7 @@ struct ResizeScaleInternal {
window: Window, window: Window,
document: Document, document: Document,
canvas: HtmlCanvasElement, canvas: HtmlCanvasElement,
style: CssStyleDeclaration, style: Style,
mql: MediaQueryListHandle, mql: MediaQueryListHandle,
observer: ResizeObserver, observer: ResizeObserver,
_observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>, _observer_closure: Closure<dyn FnMut(Array, ResizeObserver)>,
@ -65,7 +65,7 @@ impl ResizeScaleInternal {
window: Window, window: Window,
document: Document, document: Document,
canvas: HtmlCanvasElement, canvas: HtmlCanvasElement,
style: CssStyleDeclaration, style: Style,
scale_handler: S, scale_handler: S,
resize_handler: R, resize_handler: R,
) -> Rc<RefCell<Self>> ) -> Rc<RefCell<Self>>
@ -152,9 +152,7 @@ impl ResizeScaleInternal {
} }
fn notify(&mut self) { fn notify(&mut self) {
if !self.document.contains(Some(&self.canvas)) if !self.document.contains(Some(&self.canvas)) || self.style.get("display") == "none" {
|| self.style.get_property_value("display").unwrap() == "none"
{
let size = PhysicalSize::new(0, 0); let size = PhysicalSize::new(0, 0);
if self.notify_scale.replace(false) { if self.notify_scale.replace(false) {
@ -180,7 +178,7 @@ impl ResizeScaleInternal {
backend::style_size_property(&self.style, "height"), backend::style_size_property(&self.style, "height"),
); );
if self.style.get_property_value("box-sizing").unwrap() == "border-box" { if self.style.get("box-sizing") == "border-box" {
size.width -= backend::style_size_property(&self.style, "border-left-width") 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, "border-right-width")
+ backend::style_size_property(&self.style, "padding-left") + backend::style_size_property(&self.style, "padding-left")
@ -246,10 +244,7 @@ impl ResizeScaleInternal {
.get(0) .get(0)
.unchecked_into(); .unchecked_into();
let writing_mode = self let writing_mode = self.style.get("writing-mode");
.style
.get_property_value("writing-mode")
.expect("`writing-mode` is a valid CSS property");
// means the canvas is not inserted into the DOM // means the canvas is not inserted into the DOM
if writing_mode.is_empty() { if writing_mode.is_empty() {

View file

@ -196,7 +196,7 @@ impl Inner {
#[inline] #[inline]
pub fn set_cursor_icon(&self, cursor: CursorIcon) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
*self.previous_pointer.borrow_mut() = cursor.name(); *self.previous_pointer.borrow_mut() = cursor.name();
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", cursor.name()); self.canvas.borrow().style().set("cursor", cursor.name());
} }
#[inline] #[inline]
@ -223,13 +223,12 @@ impl Inner {
#[inline] #[inline]
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
if !visible { if !visible {
backend::set_canvas_style_property(self.canvas.borrow().raw(), "cursor", "none"); self.canvas.borrow().style().set("cursor", "none");
} else { } else {
backend::set_canvas_style_property( self.canvas
self.canvas.borrow().raw(), .borrow()
"cursor", .style()
&self.previous_pointer.borrow(), .set("cursor", &self.previous_pointer.borrow());
);
} }
} }