Skip to content

Commit

Permalink
Make canvas in WindowBuilder safe (#3320)
Browse files Browse the repository at this point in the history
  • Loading branch information
daxpedda authored Dec 26, 2023
1 parent 843d790 commit e0fea25
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 169 deletions.
3 changes: 1 addition & 2 deletions src/platform/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ use crate::event_loop::EventLoopWindowTarget;
use crate::platform_impl::PlatformCustomCursorBuilder;
use crate::window::CustomCursor;
use crate::window::{Window, WindowBuilder};
use crate::SendSyncWrapper;

use web_sys::HtmlCanvasElement;

Expand Down Expand Up @@ -105,7 +104,7 @@ pub trait WindowBuilderExtWebSys {

impl WindowBuilderExtWebSys for WindowBuilder {
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
self.platform_specific.canvas = SendSyncWrapper(canvas);
self.platform_specific.set_canvas(canvas);
self
}

Expand Down
4 changes: 3 additions & 1 deletion src/platform_impl/web/async/dispatcher.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::super::main_thread::MainThreadMarker;
use super::{channel, AsyncReceiver, AsyncSender, Wrapper};
use std::{
cell::Ref,
Expand All @@ -10,10 +11,11 @@ struct Closure<T>(Box<dyn FnOnce(&T) + Send>);

impl<T> Dispatcher<T> {
#[track_caller]
pub fn new(value: T) -> Option<(Self, DispatchRunner<T>)> {
pub fn new(main_thread: MainThreadMarker, value: T) -> Option<(Self, DispatchRunner<T>)> {
let (sender, receiver) = channel::<Closure<T>>();

Wrapper::new(
main_thread,
value,
|value, Closure(closure)| {
// SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything
Expand Down
6 changes: 4 additions & 2 deletions src/platform_impl/web/async/waker.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::super::main_thread::MainThreadMarker;
use super::Wrapper;
use atomic_waker::AtomicWaker;
use std::future;
Expand All @@ -19,7 +20,7 @@ struct Sender(Arc<Inner>);

impl<T> WakerSpawner<T> {
#[track_caller]
pub fn new(value: T, handler: fn(&T, usize)) -> Option<Self> {
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, usize)) -> Option<Self> {
let inner = Arc::new(Inner {
counter: AtomicUsize::new(0),
waker: AtomicWaker::new(),
Expand All @@ -31,6 +32,7 @@ impl<T> WakerSpawner<T> {
let sender = Sender(Arc::clone(&inner));

let wrapper = Wrapper::new(
main_thread,
handler,
|handler, count| {
let handler = handler.borrow();
Expand Down Expand Up @@ -86,7 +88,7 @@ impl<T> WakerSpawner<T> {

pub fn fetch(&self) -> usize {
debug_assert!(
self.0.is_main_thread(),
MainThreadMarker::new().is_some(),
"this should only be called from the main thread"
);

Expand Down
53 changes: 9 additions & 44 deletions src/platform_impl/web/async/wrapper.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use super::super::main_thread::MainThreadMarker;
use std::cell::{Ref, RefCell};
use std::future::Future;
use std::marker::PhantomData;
use std::sync::Arc;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};

// Unsafe wrapper type that allows us to use `T` when it's not `Send` from other threads.
// `value` **must** only be accessed on the main thread.
Expand Down Expand Up @@ -34,36 +33,15 @@ unsafe impl<const SYNC: bool, V> Send for Value<SYNC, V> {}
unsafe impl<V> Sync for Value<true, V> {}

impl<const SYNC: bool, V, S: Clone + Send, E> Wrapper<SYNC, V, S, E> {
thread_local! {
static MAIN_THREAD: bool = {
#[wasm_bindgen]
extern "C" {
#[derive(Clone)]
type Global;

#[wasm_bindgen(method, getter, js_name = Window)]
fn window(this: &Global) -> JsValue;
}

let global: Global = js_sys::global().unchecked_into();
!global.window().is_undefined()
};
}

#[track_caller]
pub fn new<R: Future<Output = ()>>(
_: MainThreadMarker,
value: V,
handler: fn(&RefCell<Option<V>>, E),
receiver: impl 'static + FnOnce(Arc<RefCell<Option<V>>>) -> R,
sender_data: S,
sender_handler: fn(&S, E),
) -> Option<Self> {
Self::MAIN_THREAD.with(|safe| {
if !safe {
panic!("only callable from inside the `Window`")
}
});

let value = Arc::new(RefCell::new(Some(value)));

wasm_bindgen_futures::spawn_local({
Expand All @@ -86,29 +64,16 @@ impl<const SYNC: bool, V, S: Clone + Send, E> Wrapper<SYNC, V, S, E> {
}

pub fn send(&self, event: E) {
Self::MAIN_THREAD.with(|is_main_thread| {
if *is_main_thread {
(self.handler)(&self.value.value, event)
} else {
(self.sender_handler)(&self.sender_data, event)
}
})
}

pub fn is_main_thread(&self) -> bool {
Self::MAIN_THREAD.with(|is_main_thread| *is_main_thread)
if MainThreadMarker::new().is_some() {
(self.handler)(&self.value.value, event)
} else {
(self.sender_handler)(&self.sender_data, event)
}
}

pub fn value(&self) -> Option<Ref<'_, V>> {
Self::MAIN_THREAD.with(|is_main_thread| {
if *is_main_thread {
Some(Ref::map(self.value.value.borrow(), |value| {
value.as_ref().unwrap()
}))
} else {
None
}
})
MainThreadMarker::new()
.map(|_| Ref::map(self.value.value.borrow(), |value| value.as_ref().unwrap()))
}

pub fn with_sender_data<T>(&self, f: impl FnOnce(&S) -> T) -> T {
Expand Down
Loading

0 comments on commit e0fea25

Please sign in to comment.