-
Notifications
You must be signed in to change notification settings - Fork 147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A high-level, convenient, and safe, statically typed event system #43
Comments
I'm not really sure how to handle |
I'm not sure which load event you're referring to here (this?), but the I wonder if at this layer it's perhaps okay to be quite flexible with what's possible, and only start locking things down a bit more at the higher layers? |
Yes, I mean that
As it stands, this is currently the highest level for event listening. So which "higher layer" are you referring to? A Virtual DOM library? |
@Pauan I was thinking of for example a
In order to do so you need to check Other examples could include the CustomElement API, and perhaps even some of the networking abstractions. |
@yoshuawuyts Sure, and that's handled by #10. Are you suggesting that all uses of |
@OddCoincidence asked whether we need the |
@Pauan that's putting it a bit more strongly than what I was going for, but I suspect that for mid-level APIs it's acceptable if we fail to completely constrain all interfaces, because we get a chance to do that at the high-level APIs (if we need to expose them at all). If my experience with authoring frameworks is anything to go by, I suspect it might very well be that most end-users might not ever need to interact with some of these APIs directly. |
The issue is that this is the high-level API. The mid-level API is #30 (which isn't constrained at all). However, there is one thing that convinced me that you're right: we can simply not create So that way there's no confusion: What makes this work so well is that |
I’m not certain that you can trust that Here’s an alternative design that you haven’t considered: pub trait EventType {
type Event: JsCast;
static EVENT_TYPE: &'static str;
}
pub fn on<E, F>(node: &EventTarget, _: E, callback: F) -> EventListener
where
E: EventType,
F: FnMut(E::Event) + 'static,
{
EventListener::new(node, E::EVENT_TYPE, move |e| {
callback(e.unchecked_into());
})
}
pub struct Click;
impl EventType for Click {
type Event = MouseEvent;
const EVENT_TYPE: &'static str = "click";
}
on(&foo, Click, |e| {
…
}) This eschews a wrapper type (which I think is good), at the cost of a new type to represent the event type string (which I think is pretty neutral). I haven’t considered whether type inference on the closure will cope with |
Do you have a recent example of this? Web browsers have been around for a while, and WASM is a pretty recent addition. It's the first time I'm hearing about this; having more details of when/how this happens would be of great help. |
Recently, I have no examples of DOMContentLoaded or load being fired multiple times. https://stackoverflow.com/questions/4355344/domcontentloaded-event-firing-twice-for-a-single-page-load is one example of it happening, but that’s 2010. I believe I encountered evidence of it happening sometimes in maybe 2014. Wouldn’t surprise me if browser extensions or third-party site JS fired a second load or DOMContentLoaded event because they didn’t know better, either. All up, it’s a dangerous thing to trust, so at the very least you should check, if using FnOnce, to make sure that everything doesn’t explode if it gets fired twice. If it just throws a “tried to call a freed function” exception, that’s probably OK. If it calls the wrong function in some sort of use-after-free scenario, that’s disastrous and using |
I don't think removing the wrapper type is good. The purpose of the wrapper type is to provide high-level methods, rather than the low-level raw web_sys methods. I showed how we need a high-level There's a lot of weird stuff on the web which can benefit from some Rustification. Having said that, I'm glad you posted an alternate design, since it helps contrast my proposal.
Yes, that's exactly what would happen. No undefined behavior, no memory unsafety. |
Regarding events that should only happen once:
|
Agreed for the mid-level Not sure I agree for
One possibility is to create a local export function once(target, event_type, callback) {
function listener(event) {
target.removeEventListener(event_type, listener);
callback(event);
}
target.addEventListener(event_type, listener);
} #[wasm_bindgen(module = "/events.js")]
extern {
fn once(target: &EventTarget, event_type: &str, callback: &Function);
} |
As written, this doesn't allow cancellation, but we could probably fix that. |
I guess if we tend to make API-specific wrappers for most single-use event listeners (like DOMContentLoaded) then it matters less. |
Ah, yeah, I had thought it would, but you're right it won't. Good catch! So it would have to be changed to something like this: export function on(target, event_type, callback) {
target.addEventListener(event_type, callback);
return function () {
target.removeEventListener(event_type, callback);
};
}
export function once(target, event_type, callback) {
var cancel = on(target, event_type, function (event) {
cancel();
callback(event);
});
return cancel;
} ...at that point, it may be easier to just use |
A potentially relevant fact: support for the |
@chris-morgan Do you mean using That's very interesting, I like it. web-sys already has support for it, so in that case we no longer need a JS snippet, and we don't need |
This proposal looks nice and convenient, but if aiming for a high level event API what about going a step further and make the |
@olanod yeah I think #33 covers this. FWIW, not everyone wants to use FRP, and we are trying to build things in a layered fashion so that if folks want a different high-level paradigm, they can reuse the lower-/mid-level foundation. |
I actually do plan to make a proposal about an But that can be added later, in a backwards compatible way, layered on top of this API. |
rustwasm/wasm-bindgen#1348, mentioned in the original post for this issue, has now been closed. It would be great to see this proposal move forward. |
@dcormier Note that the original problem (described in that issue) is being fixed by the browsers, so it doesn't need to be fixed by gloo: https://bugzilla.mozilla.org/show_bug.cgi?id=1541349 https://bugs.chromium.org/p/chromium/issues/detail?id=949056 |
Summary
An API that provides guaranteed static typing for events, which is both more convenient and much safer.
Motivation
#30 lays the groundwork for a mid-level event listener system, but it only supports
web_sys::Event
, and it requires passing in string event types, which are error-prone: typos can happen, and there's no guarantee that the string matches the desired type:This proposal completely solves that, by guaranteeing that the static types are always correct.
It also does automatic type casts, which is more convenient for the user (compared to the manual type casts of
EventListener
).Detailed Explanation
First, we must create a trait which supports conversion from
web_sys::Event
:Now we create various structs, one struct per event type, and impl
StaticEvent
for them:(And similarly for
MouseUpEvent
,MouseDownEvent
,KeyDownEvent
, etc.)If possible, the above should be automatically generated based upon WebIDL.
If that's not possible, we should create a macro that makes it easy to generate impls for
StaticEvent
:Lastly, there should be an
on
function which allows for actually usingStaticEvent
:And now it's possible for users to use the API:
Drawbacks, Rationale, and Alternatives
The primary drawback is the extra complexity, and the extra work of needing to create an extra struct per event type.
However, I don't see any better way to achieve the goals (full static typing, and automatic casts to the correct type).
Unresolved Questions
Perhaps the structs should accept a reference instead? In other words, it would look like this:
Does that buy us anything?
There is a very important bug which we need to solve: rustwasm/wasm-bindgen#1348
I have given a lot of thought to this, and my (tentative) conclusion is that we can solve this bug by modifying a select few APIs.
In particular, if we change the
input
,keydown
,keypress
, andkeyup
events to do unpaired surrogate checking, that might be good enough to fix everything!So, in that case,
StaticEvent
allows us to do that sort of checking:And now users can do this:
So this API is not just about convenience or static type safety, it's also about fixing unpaired surrogates!
The text was updated successfully, but these errors were encountered: