Skip to content

Commit 4f7e75d

Browse files
committed
Start implementing a "callback registry" so closures aren't leaked
1 parent fe963e4 commit 4f7e75d

File tree

3 files changed

+64
-32
lines changed

3 files changed

+64
-32
lines changed

src/platform_impl/web_sys/event_loop.rs

+56-28
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use std::collections::VecDeque;
1313
use std::marker::PhantomData;
1414
use std::rc::Rc;
1515
use wasm_bindgen::{prelude::*, JsCast};
16-
use web_sys::{EventTarget, FocusEvent, HtmlCanvasElement, KeyboardEvent, PointerEvent, WheelEvent};
16+
use web_sys::{EventTarget, FocusEvent, KeyboardEvent, PointerEvent, WheelEvent};
1717
use window::WindowId as RootWI;
1818

1919
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -27,6 +27,7 @@ impl DeviceId {
2727

2828
pub struct EventLoop<T: 'static> {
2929
elw: RootELW<T>,
30+
callbacks: CallbackRegistry<T>,
3031
}
3132

3233
pub struct EventLoopWindowTarget<T: 'static> {
@@ -71,12 +72,13 @@ struct EventLoopRunner<T> {
7172

7273
impl<T> EventLoop<T> {
7374
pub fn new() -> Self {
74-
EventLoop {
75-
elw: RootELW {
76-
p: EventLoopWindowTarget::new(),
77-
_marker: PhantomData,
78-
},
79-
}
75+
let elw = RootELW {
76+
p: EventLoopWindowTarget::new(),
77+
_marker: PhantomData,
78+
};
79+
let document = document();
80+
let callbacks = CallbackRegistry::new(elw.p.runner.clone(), document.unchecked_into());
81+
EventLoop { elw, callbacks }
8082
}
8183

8284
pub fn available_monitors(&self) -> VecDequeIter<MonitorHandle> {
@@ -87,7 +89,7 @@ impl<T> EventLoop<T> {
8789
MonitorHandle
8890
}
8991

90-
pub fn run<F>(self, mut event_handler: F) -> !
92+
pub fn run<F>(mut self, mut event_handler: F) -> !
9193
where
9294
F: 'static + FnMut(Event<T>, &RootELW<T>, &mut ControlFlow),
9395
{
@@ -99,20 +101,19 @@ impl<T> EventLoop<T> {
99101
};
100102
runner.set_listener(Box::new(move |evt, ctrl| event_handler(evt, &relw, ctrl)));
101103

102-
let document = &document();
103-
add_event(&runner, document, "blur", |elrs, _: FocusEvent| {
104+
self.callbacks.add_event("blur", |elrs, _: FocusEvent| {
104105
elrs.send_event(Event::WindowEvent {
105106
window_id: RootWI(WindowId),
106107
event: WindowEvent::Focused(false),
107108
});
108109
});
109-
add_event(&runner, document, "focus", |elrs, _: FocusEvent| {
110+
self.callbacks.add_event("focus", |elrs, _: FocusEvent| {
110111
elrs.send_event(Event::WindowEvent {
111112
window_id: RootWI(WindowId),
112113
event: WindowEvent::Focused(true),
113114
});
114115
});
115-
add_event(&runner, document, "keydown", |elrs, event: KeyboardEvent| {
116+
self.callbacks.add_event("keydown", |elrs, event: KeyboardEvent| {
116117
let key = event.key();
117118
let mut characters = key.chars();
118119
let first = characters.next();
@@ -136,7 +137,7 @@ impl<T> EventLoop<T> {
136137
},
137138
});
138139
});
139-
add_event(&runner, document, "keyup", |elrs, event: KeyboardEvent| {
140+
self.callbacks.add_event("keyup", |elrs, event: KeyboardEvent| {
140141
elrs.send_event(Event::WindowEvent {
141142
window_id: RootWI(WindowId),
142143
event: WindowEvent::KeyboardInput {
@@ -169,24 +170,43 @@ impl<T> EventLoop<T> {
169170
}
170171
}
171172

172-
pub fn register<T: 'static>(elrs: &EventLoopRunnerShared<T>, canvas: &HtmlCanvasElement) {
173-
add_event(elrs, canvas, "pointerout", |elrs, event: PointerEvent| {
173+
struct SavedCallback {
174+
pub event: &'static str,
175+
pub callback: Box<dyn AsRef<JsValue>>,
176+
}
177+
178+
/**
179+
* Manages the lifetime of the closures that are used to bind Web events to `winit` events
180+
*/
181+
pub struct CallbackRegistry<T: 'static> {
182+
runner: EventLoopRunnerShared<T>,
183+
event_target: EventTarget,
184+
callbacks: Vec<SavedCallback>,
185+
}
186+
187+
impl<T: 'static> CallbackRegistry<T> {
188+
pub fn new(runner: EventLoopRunnerShared<T>, event_target: EventTarget) -> Self {
189+
CallbackRegistry { runner, event_target, callbacks: Vec::new() }
190+
}
191+
192+
pub fn register_window_events(&mut self) {
193+
self.add_event("pointerout", |elrs, event: PointerEvent| {
174194
elrs.send_event(Event::WindowEvent {
175195
window_id: RootWI(WindowId),
176196
event: WindowEvent::CursorLeft {
177197
device_id: RootDI(DeviceId(event.pointer_id())),
178198
},
179199
});
180200
});
181-
add_event(elrs, canvas, "pointerover", |elrs, event: PointerEvent| {
201+
self.add_event("pointerover", |elrs, event: PointerEvent| {
182202
elrs.send_event(Event::WindowEvent {
183203
window_id: RootWI(WindowId),
184204
event: WindowEvent::CursorEntered {
185205
device_id: RootDI(DeviceId(event.pointer_id())),
186206
},
187207
});
188208
});
189-
add_event(elrs, canvas, "pointermove", |elrs, event: PointerEvent| {
209+
self.add_event("pointermove", |elrs, event: PointerEvent| {
190210
elrs.send_event(Event::WindowEvent {
191211
window_id: RootWI(WindowId),
192212
event: WindowEvent::CursorMoved {
@@ -199,7 +219,7 @@ pub fn register<T: 'static>(elrs: &EventLoopRunnerShared<T>, canvas: &HtmlCanvas
199219
},
200220
});
201221
});
202-
add_event(elrs, canvas, "pointerup", |elrs, event: PointerEvent| {
222+
self.add_event("pointerup", |elrs, event: PointerEvent| {
203223
elrs.send_event(Event::WindowEvent {
204224
window_id: RootWI(WindowId),
205225
event: WindowEvent::MouseInput {
@@ -210,7 +230,7 @@ pub fn register<T: 'static>(elrs: &EventLoopRunnerShared<T>, canvas: &HtmlCanvas
210230
},
211231
});
212232
});
213-
add_event(elrs, canvas, "pointerdown", |elrs, event: PointerEvent| {
233+
self.add_event("pointerdown", |elrs, event: PointerEvent| {
214234
elrs.send_event(Event::WindowEvent {
215235
window_id: RootWI(WindowId),
216236
event: WindowEvent::MouseInput {
@@ -221,7 +241,7 @@ pub fn register<T: 'static>(elrs: &EventLoopRunnerShared<T>, canvas: &HtmlCanvas
221241
},
222242
});
223243
});
224-
add_event(elrs, canvas, "wheel", |elrs, event: WheelEvent| {
244+
self.add_event("wheel", |elrs, event: WheelEvent| {
225245
let x = event.delta_x();
226246
let y = event.delta_y();
227247
let delta = match event.delta_mode() {
@@ -241,18 +261,17 @@ pub fn register<T: 'static>(elrs: &EventLoopRunnerShared<T>, canvas: &HtmlCanvas
241261
});
242262
}
243263

244-
fn add_event<T: 'static, E, F>(
245-
elrs: &EventLoopRunnerShared<T>,
246-
target: &EventTarget,
247-
event: &str,
264+
fn add_event<E, F>(
265+
&mut self,
266+
event: &'static str,
248267
mut handler: F,
249268
) where
250269
E: AsRef<web_sys::Event> + wasm_bindgen::convert::FromWasmAbi + 'static,
251270
F: FnMut(&EventLoopRunnerShared<T>, E) + 'static,
252271
{
253-
let elrs = elrs.clone();
272+
let elrs = self.runner.clone();
254273

255-
let closure = Closure::wrap(Box::new(move |event: E| {
274+
let callback = Closure::wrap(Box::new(move |event: E| {
256275
// Don't capture the event if the events loop has been destroyed
257276
match &*elrs.runner.borrow() {
258277
Some(ref runner) if runner.control == ControlFlow::Exit => return,
@@ -267,8 +286,17 @@ fn add_event<T: 'static, E, F>(
267286
handler(&elrs, event);
268287
}) as Box<dyn FnMut(E)>);
269288

270-
target.add_event_listener_with_callback(event, &closure.as_ref().unchecked_ref());
271-
closure.forget(); // TODO: don't leak this.
289+
self.event_target.add_event_listener_with_callback(event, &callback.as_ref().unchecked_ref());
290+
self.callbacks.push(SavedCallback { event, callback: Box::new(callback) });
291+
}
292+
}
293+
294+
impl<T: 'static> Drop for CallbackRegistry<T> {
295+
fn drop(&mut self) {
296+
for callback in self.callbacks.iter() {
297+
self.event_target.remove_event_listener_with_callback(callback.event, callback.callback.as_ref().as_ref().unchecked_ref());
298+
}
299+
}
272300
}
273301

274302
impl<T> ELRShared<T> {

src/platform_impl/web_sys/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod events;
55
mod window;
66

77
pub use self::event_loop::{
8-
register, DeviceId, EventLoop, EventLoopProxy, EventLoopRunnerShared, EventLoopWindowTarget,
8+
CallbackRegistry, DeviceId, EventLoop, EventLoopProxy, EventLoopRunnerShared, EventLoopWindowTarget,
99
};
1010
pub use self::events::{
1111
button_mapping, keyboard_modifiers_state, mouse_button, mouse_modifiers_state, scancode,

src/platform_impl/web_sys/window.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use platform::web_sys::WindowExtWebSys;
66
use platform_impl::platform::{document, window};
77
use monitor::{MonitorHandle as RootMH};
88
use window::{CursorIcon, Window as RootWindow, WindowAttributes, WindowId as RootWI};
9-
use super::{EventLoopWindowTarget, OsError, register};
9+
use super::{CallbackRegistry, EventLoopWindowTarget, OsError};
10+
use std::any::Any;
1011
use std::collections::VecDeque;
1112
use std::collections::vec_deque::IntoIter as VecDequeIter;
1213
use std::cell::RefCell;
@@ -51,6 +52,7 @@ pub struct Window {
5152
pub(crate) redraw: Box<dyn Fn()>,
5253
previous_pointer: RefCell<&'static str>,
5354
position: RefCell<LogicalPosition>,
55+
callbacks: Box<dyn Any>,
5456
}
5557

5658
impl Window {
@@ -64,7 +66,8 @@ impl Window {
6466
.ok_or_else(|| os_error!(OsError("Failed to find body node".to_owned())))?
6567
.append_child(&canvas).map_err(|_| os_error!(OsError("Failed to append canvas".to_owned())))?;
6668

67-
register(&target.runner, &canvas);
69+
let mut callbacks = CallbackRegistry::new(target.runner.clone(), canvas.clone().unchecked_into());
70+
callbacks.register_window_events();
6871

6972
let runner = target.runner.clone();
7073
let redraw = Box::new(move || {
@@ -85,7 +88,8 @@ impl Window {
8588
position: RefCell::new(LogicalPosition {
8689
x: 0.0,
8790
y: 0.0
88-
})
91+
}),
92+
callbacks: Box::new(callbacks),
8993
};
9094

9195
if let Some(inner_size) = attr.inner_size {

0 commit comments

Comments
 (0)