Compare commits

..

2 Commits
v0.8.9 ... 4313

Author SHA1 Message Date
Greg Johnston
f97fd557c3 fix: maintain send/syncness of the handle 2025-09-21 14:11:04 -04:00
Greg Johnston
d642806e5e fix: remove event listeners correctly when dropping handles, as documented (closes #4313) 2025-09-20 12:43:09 -04:00
4 changed files with 46 additions and 21 deletions

View File

@@ -323,7 +323,9 @@ where
fn rebuild(self, state: &mut Self::State) {
let (el, prev_cleanup) = state;
if let Some(prev) = prev_cleanup.take() {
(prev.into_inner())(el);
if let Some(remove) = prev.into_inner() {
remove();
}
}
*prev_cleanup = Some(if E::CAPTURE {
self.attach_capture(el)

View File

@@ -258,7 +258,9 @@ where
prop(self.key(), signal).rebuild(attr_state);
if let Some(prev) = prev_cleanup.take() {
(prev.into_inner())(el);
if let Some(remove) = prev.into_inner() {
remove();
}
}
*prev_cleanup = Some(self.attach(el));
}

View File

@@ -296,19 +296,20 @@ impl Dom {
// return the remover
RemoveEventHandler::new({
let name = name.to_owned();
let el = el.clone();
// safe to construct this here, because it will only run in the browser
// so it will always be accessed or dropped from the main thread
let cb = send_wrapper::SendWrapper::new(cb);
move |el: &Element| {
let cb = send_wrapper::SendWrapper::new(move || {
or_debug!(
el.remove_event_listener_with_callback(
intern(&name),
cb.as_ref().unchecked_ref()
),
el,
&el,
"removeEventListener"
)
}
});
move || cb()
})
}
@@ -334,20 +335,21 @@ impl Dom {
// return the remover
RemoveEventHandler::new({
let name = name.to_owned();
let el = el.clone();
// safe to construct this here, because it will only run in the browser
// so it will always be accessed or dropped from the main thread
let cb = send_wrapper::SendWrapper::new(cb);
move |el: &Element| {
let cb = send_wrapper::SendWrapper::new(move || {
or_debug!(
el.remove_event_listener_with_callback_and_bool(
intern(&name),
cb.as_ref().unchecked_ref(),
true
),
el,
&el,
"removeEventListener"
)
}
});
move || cb()
})
}
@@ -448,17 +450,19 @@ impl Dom {
// return the remover
RemoveEventHandler::new({
let key = key.to_owned();
let el = el.clone();
// safe to construct this here, because it will only run in the browser
// so it will always be accessed or dropped from the main thread
let cb = send_wrapper::SendWrapper::new(cb);
move |el: &Element| {
drop(cb.take());
let el_cb = send_wrapper::SendWrapper::new((el, cb));
move || {
let (el, cb) = el_cb.take();
drop(cb);
or_debug!(
js_sys::Reflect::delete_property(
el,
&el,
&JsValue::from_str(&key)
),
el,
&el,
"delete property"
);
}

View File

@@ -1,5 +1,5 @@
use crate::view::{Mountable, ToTemplate};
use std::{borrow::Cow, fmt::Debug};
use std::{borrow::Cow, fmt::Debug, marker::PhantomData};
use wasm_bindgen::JsValue;
/// A DOM renderer.
@@ -120,16 +120,33 @@ pub trait Renderer: Send + Sized + Debug + 'static {
should store it in some other data structure to clean it up \
later to avoid dropping it immediately, or leak it with \
std::mem::forget() to never drop it."]
pub struct RemoveEventHandler<T>(Box<dyn FnOnce(&T) + Send + Sync>);
#[allow(clippy::type_complexity)]
pub struct RemoveEventHandler<T>(
Option<Box<dyn FnOnce() + Send + Sync>>,
// only here to keep the generic, removing which would be a breaking change
// TODO remove generic in 0.9
PhantomData<fn() -> T>,
);
impl<T> RemoveEventHandler<T> {
/// Creates a new container with a function that will be called when it is dropped.
pub(crate) fn new(remove: impl FnOnce(&T) + Send + Sync + 'static) -> Self {
Self(Box::new(remove))
pub(crate) fn new(remove: impl FnOnce() + Send + Sync + 'static) -> Self {
Self(Some(Box::new(remove)), PhantomData)
}
pub(crate) fn into_inner(self) -> Box<dyn FnOnce(&T) + Send + Sync> {
self.0
#[allow(clippy::type_complexity)]
pub(crate) fn into_inner(
mut self,
) -> Option<Box<dyn FnOnce() + Send + Sync>> {
self.0.take()
}
}
impl<T> Drop for RemoveEventHandler<T> {
fn drop(&mut self) {
if let Some(cb) = self.0.take() {
cb()
}
}
}