Changing event system to use interning
This commit is contained in:
parent
97b39fa3ee
commit
3062359534
|
@ -20,8 +20,6 @@ futures-util-preview = "0.3.0-alpha.16"
|
|||
futures-signals = "0.3.5"
|
||||
wasm-bindgen = "0.2.45"
|
||||
js-sys = "0.3.22"
|
||||
# TODO fix this before release
|
||||
gloo = { git = "https://github.com/rustwasm/gloo" }
|
||||
|
||||
[dependencies.wasm-bindgen-futures]
|
||||
version = "0.3.22"
|
||||
|
@ -30,6 +28,7 @@ features = ["futures_0_3"]
|
|||
[dependencies.web-sys]
|
||||
version = "0.3.22"
|
||||
features = [
|
||||
"console", # TODO only needed for debug_cache
|
||||
#"CharacterData",
|
||||
"Comment",
|
||||
#"CssRule",
|
||||
|
@ -41,8 +40,8 @@ features = [
|
|||
#"DocumentFragment",
|
||||
#"DomTokenList",
|
||||
"Element",
|
||||
#"Event",
|
||||
#"EventTarget",
|
||||
"Event",
|
||||
"EventTarget",
|
||||
"FocusEvent",
|
||||
#"History",
|
||||
"InputEvent",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use wasm_bindgen::prelude::*;
|
||||
use js_sys::JsString;
|
||||
use web_sys::{HtmlElement, Element, Node, Window, Text, Comment, CssStyleSheet, CssStyleRule};
|
||||
use js_sys::{Function, JsString};
|
||||
use web_sys::{HtmlElement, Element, Node, Window, Text, Comment, CssStyleSheet, CssStyleRule, EventTarget};
|
||||
|
||||
use crate::cache::intern;
|
||||
|
||||
|
@ -67,6 +67,34 @@ use crate::cache::intern;
|
|||
export function blur(elem) { elem.blur(); }
|
||||
|
||||
export function set_property(obj, name, value) { obj[name] = value; }
|
||||
|
||||
export function add_event(elem, name, f) {
|
||||
elem.addEventListener(name, f, {
|
||||
capture: false,
|
||||
once: false,
|
||||
passive: true
|
||||
});
|
||||
}
|
||||
|
||||
export function add_event_once(elem, name, f) {
|
||||
elem.addEventListener(name, f, {
|
||||
capture: false,
|
||||
once: true,
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function add_event_preventable(elem, name, f) {
|
||||
elem.addEventListener(name, f, {
|
||||
capture: false,
|
||||
once: false,
|
||||
passive: false
|
||||
});
|
||||
}
|
||||
|
||||
export function remove_event(elem, name, f) {
|
||||
elem.removeEventListener(name, f, false);
|
||||
}
|
||||
")]
|
||||
extern "C" {
|
||||
pub(crate) fn body() -> HtmlElement;
|
||||
|
@ -118,6 +146,11 @@ extern "C" {
|
|||
|
||||
// TODO maybe use Object for obj ?
|
||||
pub(crate) fn set_property(obj: &JsValue, name: &JsString, value: &JsValue);
|
||||
|
||||
pub(crate) fn add_event(elem: &EventTarget, name: &JsString, f: &Function);
|
||||
pub(crate) fn add_event_once(elem: &EventTarget, name: &JsString, f: &Function);
|
||||
pub(crate) fn add_event_preventable(elem: &EventTarget, name: &JsString, f: &Function);
|
||||
pub(crate) fn remove_event(elem: &EventTarget, name: &JsString, f: &Function);
|
||||
}
|
||||
|
||||
|
||||
|
|
19
src/dom.rs
19
src/dom.rs
|
@ -14,7 +14,6 @@ use discard::{Discard, DiscardOnDrop};
|
|||
use wasm_bindgen::{JsValue, UnwrapThrowExt, JsCast};
|
||||
use js_sys::JsString;
|
||||
use web_sys::{HtmlElement, Node, EventTarget, Element, CssStyleSheet, CssStyleRule};
|
||||
use gloo::events::{EventListener, EventListenerOptions};
|
||||
|
||||
use crate::cache::intern;
|
||||
use crate::bindings;
|
||||
|
@ -22,7 +21,7 @@ use crate::callbacks::Callbacks;
|
|||
use crate::traits::*;
|
||||
use crate::operations;
|
||||
use crate::operations::{for_each, spawn_future};
|
||||
use crate::utils::{on, on_with_options, ValueDiscard, FnDiscard, EventDiscard};
|
||||
use crate::utils::{EventListener, on, on_preventable, ValueDiscard, FnDiscard};
|
||||
|
||||
|
||||
pub struct RefFn<A, B, C> where B: ?Sized {
|
||||
|
@ -152,7 +151,7 @@ impl Signal for IsWindowLoaded {
|
|||
|
||||
*self = IsWindowLoaded::Pending {
|
||||
receiver,
|
||||
_event: EventListener::once(&bindings::window(), "load", move |_| {
|
||||
_event: EventListener::once(bindings::window().into(), "load", move |_| {
|
||||
// TODO test this
|
||||
sender.send(Some(true)).unwrap_throw();
|
||||
}),
|
||||
|
@ -394,17 +393,17 @@ impl<A> DomBuilder<A> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn _event<T, F>(&mut self, element: &EventTarget, listener: F)
|
||||
fn _event<T, F>(&mut self, element: EventTarget, listener: F)
|
||||
where T: StaticEvent,
|
||||
F: FnMut(T) + 'static {
|
||||
self.callbacks.after_remove(EventDiscard::new(on(element, listener)));
|
||||
self.callbacks.after_remove(on(element, listener));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn _event_with_options<T, F>(&mut self, element: &EventTarget, options: EventListenerOptions, listener: F)
|
||||
fn _event_preventable<T, F>(&mut self, element: EventTarget, listener: F)
|
||||
where T: StaticEvent,
|
||||
F: FnMut(T) + 'static {
|
||||
self.callbacks.after_remove(EventDiscard::new(on_with_options(element, options, listener)));
|
||||
self.callbacks.after_remove(on_preventable(element, listener));
|
||||
}
|
||||
|
||||
// TODO add this to the StylesheetBuilder and ClassBuilder too
|
||||
|
@ -412,7 +411,7 @@ impl<A> DomBuilder<A> {
|
|||
pub fn global_event<T, F>(mut self, listener: F) -> Self
|
||||
where T: StaticEvent,
|
||||
F: FnMut(T) + 'static {
|
||||
self._event(&bindings::window(), listener);
|
||||
self._event(bindings::window().into(), listener);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -518,7 +517,7 @@ impl<A> DomBuilder<A> where A: AsRef<EventTarget> {
|
|||
where T: StaticEvent,
|
||||
F: FnMut(T) + 'static {
|
||||
// TODO can this clone be avoided ?
|
||||
self._event(&self.element.as_ref().clone(), listener);
|
||||
self._event(self.element.as_ref().clone(), listener);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -527,7 +526,7 @@ impl<A> DomBuilder<A> where A: AsRef<EventTarget> {
|
|||
where T: StaticEvent,
|
||||
F: FnMut(T) + 'static {
|
||||
// TODO can this clone be avoided ?
|
||||
self._event_with_options(&self.element.as_ref().clone(), EventListenerOptions::enable_prevent_default(), listener);
|
||||
self._event_preventable(self.element.as_ref().clone(), listener);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ use js_sys::JsString;
|
|||
use web_sys::{EventTarget, HtmlElement};
|
||||
use lazy_static::lazy_static;
|
||||
use futures_signals::signal::{Mutable, ReadOnlyMutable};
|
||||
use gloo::events::EventListener;
|
||||
|
||||
use crate::bindings;
|
||||
use crate::dom::{Dom, DomBuilder};
|
||||
use crate::utils::EventListener;
|
||||
use crate::events;
|
||||
|
||||
|
||||
|
@ -35,12 +35,12 @@ impl CurrentUrl {
|
|||
let value = Mutable::new(String::from(bindings::current_url()));
|
||||
|
||||
// TODO clean this up somehow ?
|
||||
EventListener::new(&bindings::window(), "popstate", {
|
||||
let _ = EventListener::new(bindings::window().into(), "popstate", {
|
||||
let value = value.clone();
|
||||
move |_| {
|
||||
change_url(&value);
|
||||
}
|
||||
}).forget();
|
||||
});
|
||||
|
||||
Self {
|
||||
value,
|
||||
|
|
104
src/utils.rs
104
src/utils.rs
|
@ -1,13 +1,77 @@
|
|||
use std::mem::ManuallyDrop;
|
||||
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use js_sys::JsString;
|
||||
use discard::Discard;
|
||||
use web_sys::{EventTarget};
|
||||
use gloo::events::{EventListener, EventListenerOptions};
|
||||
use web_sys::{EventTarget, Event};
|
||||
|
||||
use crate::bindings;
|
||||
use crate::cache::intern;
|
||||
use crate::traits::StaticEvent;
|
||||
|
||||
|
||||
pub(crate) fn on<E, F>(element: &EventTarget, mut callback: F) -> EventListener
|
||||
// TODO should use gloo::events, but it doesn't support interning or Discard
|
||||
pub(crate) struct EventListener {
|
||||
elem: EventTarget,
|
||||
name: JsString,
|
||||
closure: Option<Closure<dyn FnMut(&Event)>>,
|
||||
}
|
||||
|
||||
// TODO should these inline ?
|
||||
impl EventListener {
|
||||
#[inline]
|
||||
pub(crate) fn new<F>(elem: EventTarget, name: &str, callback: F) -> Self where F: FnMut(&Event) + 'static {
|
||||
let closure = Closure::wrap(Box::new(callback) as Box<dyn FnMut(&Event)>);
|
||||
let name = intern(name);
|
||||
|
||||
bindings::add_event(&elem, &name, closure.as_ref().unchecked_ref());
|
||||
|
||||
Self { elem, name, closure: Some(closure) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn new_preventable<F>(elem: EventTarget, name: &str, callback: F) -> Self where F: FnMut(&Event) + 'static {
|
||||
let closure = Closure::wrap(Box::new(callback) as Box<dyn FnMut(&Event)>);
|
||||
let name = intern(name);
|
||||
|
||||
bindings::add_event_preventable(&elem, &name, closure.as_ref().unchecked_ref());
|
||||
|
||||
Self { elem, name, closure: Some(closure) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn once<F>(elem: EventTarget, name: &str, callback: F) -> Self where F: FnOnce(&Event) + 'static {
|
||||
let closure = Closure::once(callback);
|
||||
let name = intern(name);
|
||||
|
||||
bindings::add_event_once(&elem, &name, closure.as_ref().unchecked_ref());
|
||||
|
||||
Self { elem, name, closure: Some(closure) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventListener {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
if let Some(closure) = self.closure.take() {
|
||||
// TODO can this be made more optimal ?
|
||||
closure.forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Discard for EventListener {
|
||||
#[inline]
|
||||
fn discard(mut self) {
|
||||
let closure = self.closure.take().unwrap_throw();
|
||||
bindings::remove_event(&self.elem, &self.name, closure.as_ref().unchecked_ref());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn on<E, F>(element: EventTarget, mut callback: F) -> EventListener
|
||||
where E: StaticEvent,
|
||||
F: FnMut(E) + 'static {
|
||||
EventListener::new(element, E::EVENT_TYPE, move |e| {
|
||||
|
@ -15,10 +79,11 @@ pub(crate) fn on<E, F>(element: &EventTarget, mut callback: F) -> EventListener
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn on_with_options<E, F>(element: &EventTarget, options: EventListenerOptions, mut callback: F) -> EventListener
|
||||
#[inline]
|
||||
pub(crate) fn on_preventable<E, F>(element: EventTarget, mut callback: F) -> EventListener
|
||||
where E: StaticEvent,
|
||||
F: FnMut(E) + 'static {
|
||||
EventListener::new_with_options(element, E::EVENT_TYPE, options, move |e| {
|
||||
EventListener::new_preventable(element, E::EVENT_TYPE, move |e| {
|
||||
callback(E::unchecked_from_event(e.clone()));
|
||||
})
|
||||
}
|
||||
|
@ -61,32 +126,3 @@ impl<A> Discard for FnDiscard<A> where A: FnOnce() {
|
|||
self.0();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO verify that this doesn't leak events / memory
|
||||
// TODO is this worth using ?
|
||||
pub(crate) struct EventDiscard(Option<EventListener>);
|
||||
|
||||
impl EventDiscard {
|
||||
#[inline]
|
||||
pub(crate) fn new(listener: EventListener) -> Self {
|
||||
EventDiscard(Some(listener))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventDiscard {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
// TODO does this cleanup as much memory as possible ?
|
||||
if let Some(listener) = self.0.take() {
|
||||
listener.forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Discard for EventDiscard {
|
||||
#[inline]
|
||||
fn discard(mut self) {
|
||||
self.0.take();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue