diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fb2a16f..3ff82497 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ And please only add new entries to the top of this list, right below the `# Unre - On Web, implement `WindowEvent::Occluded`. - On Web, fix touch location to be as accurate as mouse position. - On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position. +- On Web, add Fullscreen API compatibility for Safari. # 0.29.0-beta.0 diff --git a/clippy.toml b/clippy.toml index de970d87..9ba7d207 100644 --- a/clippy.toml +++ b/clippy.toml @@ -6,4 +6,7 @@ disallowed-methods = [ { 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" }, + { 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::fullscreen_element", reason = "Doesn't account for compatibility with Safari" }, ] diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 14f842fc..3fc61d1f 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -3,7 +3,7 @@ use super::event_handle::EventListenerHandle; use super::intersection_handle::IntersectionObserverHandle; use super::media_query_handle::MediaQueryListHandle; use super::pointer::PointerHandler; -use super::{event, ButtonsState, ResizeScaleHandle}; +use super::{event, fullscreen, ButtonsState, ResizeScaleHandle}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{Force, MouseButton, MouseScrollDelta}; @@ -18,8 +18,7 @@ use std::sync::Arc; use js_sys::Promise; use smol_str::SmolStr; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::{closure::Closure, JsCast, JsValue}; +use wasm_bindgen::{closure::Closure, JsCast}; use wasm_bindgen_futures::JsFuture; use web_sys::{ CssStyleDeclaration, Document, Event, FocusEvent, HtmlCanvasElement, KeyboardEvent, WheelEvent, @@ -504,27 +503,15 @@ impl Common { handler(event); if *wants_fullscreen.borrow() { - canvas - .request_fullscreen() - .expect("Failed to enter fullscreen"); + fullscreen::request_fullscreen(&canvas).expect("Failed to enter fullscreen"); *wants_fullscreen.borrow_mut() = false; } }) } pub fn request_fullscreen(&self) { - #[wasm_bindgen] - extern "C" { - type ElementExt; - - #[wasm_bindgen(catch, method, js_name = requestFullscreen)] - fn request_fullscreen(this: &ElementExt) -> Result; - } - - let raw: &ElementExt = self.raw.unchecked_ref(); - // This should return a `Promise`, but Safari v<16.4 is not up-to-date with the spec. - match raw.request_fullscreen() { + match fullscreen::request_fullscreen(&self.raw) { Ok(value) if !value.is_undefined() => { let promise: Promise = value.unchecked_into(); let wants_fullscreen = self.wants_fullscreen.clone(); diff --git a/src/platform_impl/web/web_sys/fullscreen.rs b/src/platform_impl/web/web_sys/fullscreen.rs new file mode 100644 index 00000000..00e2df6a --- /dev/null +++ b/src/platform_impl/web/web_sys/fullscreen.rs @@ -0,0 +1,99 @@ +use once_cell::unsync::OnceCell; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsCast, JsValue}; +use web_sys::{Document, Element, HtmlCanvasElement}; + +thread_local! { + static FULLSCREEN_API_SUPPORT: OnceCell = OnceCell::new(); +} + +fn canvas_has_fullscreen_api_support(canvas: &HtmlCanvasElement) -> bool { + FULLSCREEN_API_SUPPORT.with(|support| { + *support.get_or_init(|| { + #[wasm_bindgen] + extern "C" { + type CanvasFullScreenApiSupport; + + #[wasm_bindgen(method, getter, js_name = requestFullscreen)] + fn has_request_fullscreen(this: &CanvasFullScreenApiSupport) -> JsValue; + } + + let support: &CanvasFullScreenApiSupport = canvas.unchecked_ref(); + !support.has_request_fullscreen().is_undefined() + }) + }) +} + +fn document_has_fullscreen_api_support(document: &Document) -> bool { + FULLSCREEN_API_SUPPORT.with(|support| { + *support.get_or_init(|| { + #[wasm_bindgen] + extern "C" { + type DocumentFullScreenApiSupport; + + #[wasm_bindgen(method, getter, js_name = exitFullscreen)] + fn has_exit_fullscreen(this: &DocumentFullScreenApiSupport) -> JsValue; + } + + let support: &DocumentFullScreenApiSupport = document.unchecked_ref(); + !support.has_exit_fullscreen().is_undefined() + }) + }) +} + +pub fn request_fullscreen(canvas: &HtmlCanvasElement) -> Result { + #[wasm_bindgen] + extern "C" { + type RequestFullscreen; + + #[wasm_bindgen(catch, method, js_name = requestFullscreen)] + fn request_fullscreen(this: &RequestFullscreen) -> Result; + + #[wasm_bindgen(catch, method, js_name = webkitRequestFullscreen)] + fn webkit_request_fullscreen(this: &RequestFullscreen) -> Result; + } + + let element: &RequestFullscreen = canvas.unchecked_ref(); + + if canvas_has_fullscreen_api_support(canvas) { + element.request_fullscreen() + } else { + element.webkit_request_fullscreen() + } +} + +pub fn exit_fullscreen(document: &Document) { + #[wasm_bindgen] + extern "C" { + type ExitFullscreen; + + #[wasm_bindgen(method, js_name = webkitExitFullscreen)] + fn webkit_exit_fullscreen(this: &ExitFullscreen); + } + + if document_has_fullscreen_api_support(document) { + #[allow(clippy::disallowed_methods)] + document.exit_fullscreen() + } else { + let document: &ExitFullscreen = document.unchecked_ref(); + document.webkit_exit_fullscreen() + } +} + +pub fn fullscreen_element(document: &Document) -> Option { + #[wasm_bindgen] + extern "C" { + type FullscreenElement; + + #[wasm_bindgen(method, getter, js_name = webkitFullscreenElement)] + fn webkit_fullscreen_element(this: &FullscreenElement) -> Option; + } + + if document_has_fullscreen_api_support(document) { + #[allow(clippy::disallowed_methods)] + document.fullscreen_element() + } else { + let document: &FullscreenElement = document.unchecked_ref(); + document.webkit_fullscreen_element() + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index a45a65ac..c30215c9 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -1,6 +1,7 @@ mod canvas; pub mod event; mod event_handle; +mod fullscreen; mod intersection_handle; mod media_query_handle; mod pointer; @@ -26,7 +27,7 @@ pub fn throw(msg: &str) { } pub fn exit_fullscreen(document: &Document) { - document.exit_fullscreen(); + fullscreen::exit_fullscreen(document); } pub struct PageTransitionEventHandle { @@ -115,7 +116,7 @@ pub fn set_canvas_style_property(raw: &HtmlCanvasElement, property: &str, value: } pub fn is_fullscreen(document: &Document, canvas: &HtmlCanvasElement) -> bool { - match document.fullscreen_element() { + match fullscreen::fullscreen_element(document) { Some(elem) => { let canvas: &Element = canvas; canvas == &elem