From 809b130efb0e52b5b2c7007510ef02d26b9f723f Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sun, 3 Sep 2023 08:40:45 -0700 Subject: [PATCH] Add wasm-bindgen v0.2 handles for web_sys::HtmlCanvasElement and web_sys::OffscreenCanvas (#134) * Add wasm-bindgen v0.2 handles * Replace wasm-bindgen handles with real wbg objects In response to feedback from the community, this sets up the wasm- bindgen handles to use direct wasm-bindgen objects rather than indices. As wasm-bindgen handles are not Copy, Eq or Hash, this commit also removes those impls from all handles. Closes #142 as well Signed-off-by: John Nunley * Fixup CI Signed-off-by: John Nunley * Whoops Signed-off-by: John Nunley * Rename feature to unstable_web_handles_wbg_02 Signed-off-by: John Nunley * Use a raw pointer instead of the direct JsValue Signed-off-by: John Nunley * Fix CI issues Signed-off-by: John Nunley * Review comments Signed-off-by: John Nunley * Fix wasm feature name and structs names * Re-add Copy * Redo web canvas examples * Documentation tweaks * Add safety comment about ABI * Add From impls for the new web handles --------- Signed-off-by: John Nunley Co-authored-by: Mads Marquart --- .github/workflows/ci.yml | 16 ++--- Cargo.toml | 9 +++ src/borrowed.rs | 6 +- src/lib.rs | 28 +++++++- src/web.rs | 147 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c997113..36f629c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,19 +40,15 @@ jobs: with: rust-version: ${{ matrix.rust_version }} - - run: rustup target add x86_64-linux-android + - run: rustup target add wasm32-unknown-unknown - name: Check documentation run: cargo doc --no-deps --document-private-items - - name: Run tests - run: cargo test --verbose - - - name: Run tests with std - run: cargo test --verbose --features std + - uses: taiki-e/install-action@cargo-hack - - name: Check on Android - run: cargo check --verbose --target x86_64-linux-android + - name: Run tests + run: cargo hack test --feature-powerset - - name: Check on Android with std - run: cargo check --verbose --target x86_64-linux-android --features std + - name: Run tests for wasm32-unknown-unknown + run: cargo hack check --target wasm32-unknown-unknown --feature-powerset diff --git a/Cargo.toml b/Cargo.toml index 675e28e..a13cf13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,15 @@ rust-version = "1.64" alloc = [] std = ["alloc"] +# Allow conversion methods to/from WASM types using `wasm-bindgen` v0.2. +wasm-bindgen-0-2 = ["wasm-bindgen", "std"] + +[target.'cfg(target_family = "wasm")'.dependencies.wasm-bindgen] +version = "0.2.87" +default-features = false +features = ["std"] +optional = true + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/src/borrowed.rs b/src/borrowed.rs index 9e3d3a0..9ee9d58 100644 --- a/src/borrowed.rs +++ b/src/borrowed.rs @@ -209,7 +209,7 @@ impl HasWindowHandle for alloc::sync::Arc { /// /// This handle is guaranteed to be safe and valid. Get the underlying raw window handle with the /// [`HasRawWindowHandle`] trait. -#[derive(PartialEq, Eq, Hash, Clone)] +#[derive(PartialEq, Eq, Hash, Copy, Clone)] pub struct WindowHandle<'a> { raw: RawWindowHandle, _marker: PhantomData<&'a *const ()>, @@ -237,13 +237,13 @@ impl<'a> WindowHandle<'a> { unsafe impl HasRawWindowHandle for WindowHandle<'_> { fn raw_window_handle(&self) -> Result { - Ok(self.raw.clone()) + Ok(self.raw) } } impl HasWindowHandle for WindowHandle<'_> { fn window_handle(&self) -> Result { - Ok(self.clone()) + Ok(*self) } } diff --git a/src/lib.rs b/src/lib.rs index 3b81564..4bf6912 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] +#![allow(clippy::new_without_default)] +#![deny(unsafe_op_in_unsafe_fn)] //! Interoperability library for Rust Windowing applications. //! @@ -51,7 +53,9 @@ pub use unix::{ DrmDisplayHandle, DrmWindowHandle, GbmDisplayHandle, GbmWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, XcbDisplayHandle, XcbWindowHandle, XlibDisplayHandle, XlibWindowHandle, }; -pub use web::{WebDisplayHandle, WebWindowHandle}; +pub use web::{ + WebCanvasWindowHandle, WebDisplayHandle, WebOffscreenCanvasWindowHandle, WebWindowHandle, +}; pub use windows::{Win32WindowHandle, WinRtWindowHandle, WindowsDisplayHandle}; use core::fmt; @@ -120,7 +124,7 @@ unsafe impl HasRawWindowHandle for alloc::sync:: /// [`RawWindowHandle::Xlib`] on macOS, it would just be weird, and probably /// requires something like XQuartz be used). #[non_exhaustive] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum RawWindowHandle { /// A raw window handle for UIKit (Apple's non-macOS windowing library). /// @@ -189,6 +193,20 @@ pub enum RawWindowHandle { /// ## Availability Hints /// This variant is used on Wasm or asm.js targets when targeting the Web/HTML5. Web(WebWindowHandle), + /// A raw window handle for a Web canvas registered via [`wasm-bindgen`]. + /// + /// ## Availability Hints + /// This variant is used on Wasm or asm.js targets when targeting the Web/HTML5. + /// + /// [`wasm-bindgen`]: https://crates.io/crates/wasm-bindgen + WebCanvas(WebCanvasWindowHandle), + /// A raw window handle for a Web offscreen canvas registered via [`wasm-bindgen`]. + /// + /// ## Availability Hints + /// This variant is used on Wasm or asm.js targets when targeting the Web/HTML5. + /// + /// [`wasm-bindgen`]: https://crates.io/crates/wasm-bindgen + WebOffscreenCanvas(WebOffscreenCanvasWindowHandle), /// A raw window handle for Android NDK. /// /// ## Availability Hints @@ -431,5 +449,11 @@ from_impl!(RawWindowHandle, Gbm, GbmWindowHandle); from_impl!(RawWindowHandle, Win32, Win32WindowHandle); from_impl!(RawWindowHandle, WinRt, WinRtWindowHandle); from_impl!(RawWindowHandle, Web, WebWindowHandle); +from_impl!(RawWindowHandle, WebCanvas, WebCanvasWindowHandle); +from_impl!( + RawWindowHandle, + WebOffscreenCanvas, + WebOffscreenCanvasWindowHandle +); from_impl!(RawWindowHandle, AndroidNdk, AndroidNdkWindowHandle); from_impl!(RawWindowHandle, Haiku, HaikuWindowHandle); diff --git a/src/web.rs b/src/web.rs index 33d7e87..9e782a8 100644 --- a/src/web.rs +++ b/src/web.rs @@ -1,3 +1,6 @@ +use core::ffi::c_void; +use core::ptr::NonNull; + /// Raw display handle for the Web. #[non_exhaustive] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -48,3 +51,147 @@ impl WebWindowHandle { Self { id } } } + +/// Raw window handle for a Web canvas registered via [`wasm-bindgen`]. +/// +/// [`wasm-bindgen`]: https://crates.io/crates/wasm-bindgen +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct WebCanvasWindowHandle { + /// A pointer to the [`JsValue`] of an [`HtmlCanvasElement`]. + /// + /// Note: This uses [`c_void`] to avoid depending on `wasm-bindgen` + /// directly. + /// + /// [`JsValue`]: https://docs.rs/wasm-bindgen/latest/wasm_bindgen/struct.JsValue.html + /// [`HtmlCanvasElement`]: https://docs.rs/web-sys/latest/web_sys/struct.HtmlCanvasElement.html + // + // SAFETY: Not using `JsValue` is sound because `wasm-bindgen` guarantees + // that there's only one version of itself in any given binary, and hence + // we can't have a type-confusion where e.g. one library used `JsValue` + // from `v0.2` of `wasm-bindgen`, and another used `JsValue` from `v1.0`; + // the binary will simply fail to compile! + // + // Reference: TODO + pub obj: NonNull, +} + +impl WebCanvasWindowHandle { + /// Create a new handle from a pointer to [`HtmlCanvasElement`]. + /// + /// [`HtmlCanvasElement`]: https://docs.rs/web-sys/latest/web_sys/struct.HtmlCanvasElement.html + /// + /// # Example + /// + /// ``` + /// # use core::ffi::c_void; + /// # use core::ptr::NonNull; + /// # use raw_window_handle::WebCanvasWindowHandle; + /// # type HtmlCanvasElement = (); + /// # type JsValue = (); + /// let canvas: &HtmlCanvasElement; + /// # canvas = &(); + /// let value: &JsValue = &canvas; // Deref to `JsValue` + /// let obj: NonNull = NonNull::from(value).cast(); + /// let mut handle = WebCanvasWindowHandle::new(obj); + /// ``` + pub fn new(obj: NonNull) -> Self { + Self { obj } + } +} + +#[cfg(all(target_family = "wasm", feature = "wasm-bindgen-0-2"))] +#[cfg_attr( + docsrs, + doc(cfg(all(target_family = "wasm", feature = "wasm-bindgen-0-2"))) +)] +/// These implementations are only available when `wasm-bindgen-0-2` is enabled. +impl WebCanvasWindowHandle { + /// Create a new `WebCanvasWindowHandle` from a [`wasm_bindgen::JsValue`]. + /// + /// The `JsValue` should refer to a `HtmlCanvasElement`, and the lifetime + /// of the value should be at least as long as the lifetime of this. + pub fn from_wasm_bindgen_0_2(js_value: &wasm_bindgen::JsValue) -> Self { + Self::new(NonNull::from(js_value).cast()) + } + + /// Convert to the underlying [`wasm_bindgen::JsValue`]. + /// + /// # Safety + /// + /// The inner pointer must be valid. This is ensured if this handle was + /// borrowed from [`WindowHandle`][crate::WindowHandle]. + pub unsafe fn as_wasm_bindgen_0_2(&self) -> &wasm_bindgen::JsValue { + unsafe { self.obj.cast().as_ref() } + } +} + +/// Raw window handle for a Web offscreen canvas registered via +/// [`wasm-bindgen`]. +/// +/// [`wasm-bindgen`]: https://crates.io/crates/wasm-bindgen +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct WebOffscreenCanvasWindowHandle { + /// A pointer to the [`JsValue`] of an [`OffscreenCanvas`]. + /// + /// Note: This uses [`c_void`] to avoid depending on `wasm-bindgen` + /// directly. + /// + /// [`JsValue`]: https://docs.rs/wasm-bindgen/latest/wasm_bindgen/struct.JsValue.html + /// [`OffscreenCanvas`]: https://docs.rs/web-sys/latest/web_sys/struct.OffscreenCanvas.html + // + // SAFETY: See WebCanvasWindowHandle. + pub obj: NonNull, +} + +impl WebOffscreenCanvasWindowHandle { + /// Create a new handle from a pointer to an [`OffscreenCanvas`]. + /// + /// [`OffscreenCanvas`]: https://docs.rs/web-sys/latest/web_sys/struct.OffscreenCanvas.html + /// + /// # Example + /// + /// ``` + /// # use core::ffi::c_void; + /// # use core::ptr::NonNull; + /// # use raw_window_handle::WebOffscreenCanvasWindowHandle; + /// # type OffscreenCanvas = (); + /// # type JsValue = (); + /// let canvas: &OffscreenCanvas; + /// # canvas = &(); + /// let value: &JsValue = &canvas; // Deref to `JsValue` + /// let obj: NonNull = NonNull::from(value).cast(); + /// let mut handle = WebOffscreenCanvasWindowHandle::new(obj); + /// ``` + pub fn new(obj: NonNull) -> Self { + Self { obj } + } +} + +#[cfg(all(target_family = "wasm", feature = "wasm-bindgen-0-2"))] +#[cfg_attr( + docsrs, + doc(cfg(all(target_family = "wasm", feature = "wasm-bindgen-0-2"))) +)] +/// These implementations are only available when `wasm-bindgen-0-2` is enabled. +impl WebOffscreenCanvasWindowHandle { + /// Create a new `WebOffscreenCanvasWindowHandle` from a + /// [`wasm_bindgen::JsValue`]. + /// + /// The `JsValue` should refer to a `HtmlCanvasElement`, and the lifetime + /// of the value should be at least as long as the lifetime of this. + pub fn from_wasm_bindgen_0_2(js_value: &wasm_bindgen::JsValue) -> Self { + Self::new(NonNull::from(js_value).cast()) + } + + /// Convert to the underlying [`wasm_bindgen::JsValue`]. + /// + /// # Safety + /// + /// The inner pointer must be valid. This is ensured if this handle was + /// borrowed from [`WindowHandle`][crate::WindowHandle]. + pub unsafe fn as_wasm_bindgen_0_2(&self) -> &wasm_bindgen::JsValue { + unsafe { self.obj.cast().as_ref() } + } +}