winit-web: return immediately from run_app on web

This avoids using JavaScript exceptions to support `EventLoop::run_app`
on the web, which is a huge hack, and doesn't work with the Exception
Handling Proposal for WebAssembly:
https://github.com/WebAssembly/exception-handling

This needs the application handler passed to `run_app` to be `'static`,
but that works better on iOS too anyhow (since you can't accidentally
forget to pass in state that then wouldn't be dropped when terminating).
This commit is contained in:
Mads Marquart 2025-03-17 04:49:58 +01:00 committed by Kirill Chibisov
parent e8bccfff4f
commit f69b601abb
9 changed files with 81 additions and 102 deletions

View file

@ -39,32 +39,9 @@ impl EventLoop {
EVENT_LOOP_CREATED.store(false, Ordering::Relaxed);
}
pub fn run_app<A: ApplicationHandler>(self, app: A) -> ! {
let app = Box::new(app);
// SAFETY: The `transmute` is necessary because `run()` requires `'static`. This is safe
// because this function will never return and all resources not cleaned up by the point we
// `throw` will leak, making this actually `'static`.
let app = unsafe {
std::mem::transmute::<
Box<dyn ApplicationHandler + '_>,
Box<dyn ApplicationHandler + 'static>,
>(app)
};
self.elw.run(app, false);
// Throw an exception to break out of Rust execution and use unreachable to tell the
// compiler this function won't return, giving it a return type of '!'
backend::throw(
"Using exceptions for control flow, don't mind me. This isn't actually an error!",
);
unreachable!();
}
pub fn spawn_app<A: ApplicationHandler + 'static>(self, app: A) {
self.elw.run(Box::new(app), true);
pub fn run_app<A: ApplicationHandler + 'static>(self, app: A) -> Result<(), EventLoopError> {
self.elw.run(Box::new(app));
Ok(())
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {

View file

@ -48,7 +48,6 @@ struct Execution {
exit: Cell<bool>,
runner: RefCell<RunnerEnum>,
suspended: Cell<bool>,
event_loop_recreation: Cell<bool>,
events: RefCell<VecDeque<Event>>,
id: Cell<usize>,
window: web_sys::Window,
@ -195,7 +194,6 @@ impl Shared {
exit: Cell::new(false),
runner: RefCell::new(RunnerEnum::Pending),
suspended: Cell::new(false),
event_loop_recreation: Cell::new(false),
events: RefCell::new(VecDeque::new()),
window,
navigator,
@ -772,9 +770,7 @@ impl Shared {
// * For each undropped `Window`:
// * The `register_redraw_request` closure.
// * The `destroy_fn` closure.
if self.0.event_loop_recreation.get() {
EventLoop::allow_event_loop_recreation();
}
EventLoop::allow_event_loop_recreation();
}
// Check if the event loop is currently closed
@ -809,10 +805,6 @@ impl Shared {
}
}
pub fn event_loop_recreation(&self, allow: bool) {
self.0.event_loop_recreation.set(allow)
}
pub(crate) fn control_flow(&self) -> ControlFlow {
self.0.control_flow.get()
}

View file

@ -56,8 +56,7 @@ impl ActiveEventLoop {
Self { runner: runner::Shared::new(), modifiers: ModifiersShared::default() }
}
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>, event_loop_recreation: bool) {
self.runner.event_loop_recreation(event_loop_recreation);
pub(crate) fn run(&self, app: Box<dyn ApplicationHandler>) {
self.runner.start(app, self.clone());
}

View file

@ -85,7 +85,6 @@ use std::task::{Context, Poll};
use ::web_sys::HtmlCanvasElement;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::NotSupportedError;
use winit_core::event_loop::ActiveEventLoop;
@ -237,30 +236,6 @@ impl Default for WindowAttributesWeb {
/// Additional methods on `EventLoop` that are specific to the Web.
pub trait EventLoopExtWeb {
/// Initializes the winit event loop.
///
/// Unlike
#[cfg_attr(target_feature = "exception-handling", doc = "`run_app()`")]
#[cfg_attr(
not(target_feature = "exception-handling"),
doc = "[`run_app()`]"
)]
/// [^1], this returns immediately, and doesn't throw an exception in order to
/// satisfy its [`!`] return type.
///
/// Once the event loop has been destroyed, it's possible to reinitialize another event loop
/// by calling this function again. This can be useful if you want to recreate the event loop
/// while the WebAssembly module is still loaded. For example, this can be used to recreate the
/// event loop when switching between tabs on a single page application.
#[rustfmt::skip]
///
#[cfg_attr(
not(target_feature = "exception-handling"),
doc = "[`run_app()`]: EventLoop::run_app()"
)]
/// [^1]: `run_app()` is _not_ available on Wasm when the target supports `exception-handling`.
fn spawn_app<A: ApplicationHandler + 'static>(self, app: A);
/// Sets the strategy for [`ControlFlow::Poll`].
///
/// See [`PollStrategy`].

View file

@ -25,10 +25,6 @@ pub use self::resize_scaling::ResizeScaleHandle;
pub use self::safe_area::SafeAreaHandle;
pub use self::schedule::Schedule;
pub fn throw(msg: &str) {
wasm_bindgen::throw_str(msg);
}
pub struct PageTransitionEventHandle {
_show_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,
_hide_listener: event_handle::EventListenerHandle<dyn FnMut(PageTransitionEvent)>,