Changing event system to use interning

This commit is contained in:
Pauan 2019-06-21 20:58:39 +02:00
parent 97b39fa3ee
commit 3062359534
5 changed files with 120 additions and 53 deletions

View File

@ -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",

View File

@ -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);
}

View File

@ -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
}
}

View File

@ -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,

View File

@ -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();
}
}