WICG / input-for-workers

Specification for exposing input events to workers
Other
25 stars 6 forks source link

Consider to have EventPort #6

Open smaug---- opened 5 years ago

smaug---- commented 5 years ago

Similar to MessagePort, if the API had an EventPort, it could be sent to subworkers and such. And EventPort could have a way filter out unnecessary events, or probably rather opt-in to the events the port should get. EventPort could also have a name, so one wouldn't need to create any new magical delegatedTarget object.

NavidZ commented 5 years ago

cc @majido @mustaq

Can you clarify a little more about what the EventPort would look like?

So is it like the main thread will create an EventPort object (which is being taken from a DOM node that can be targeted). Then it passes that to the worker thread with postMessage and then the worker can add event listeners and whatnot on the EventPort object?

smaug---- commented 5 years ago

Yes, pretty much that. Something like:


[Exposed=(Window,Worker,AudioWorklet), Transferable]
interface EventPort : EventTarget {
  constructor(DOMString name, EventTarget emitter, sequence<DOMString> events);
  readonly attribute DOMString name; 
  void start();
  void close();
}
NavidZ commented 5 years ago

I like a few properties of this proposal you are suggesting:

  1. We are sending the "event carrier object" (regardless of what we call it) through existing mechanisms (i.e. postMessage).
  2. It can be retransferred through the same mechanism to other threads.

A few notes:

smaug---- commented 5 years ago

Yeah, name might not be needed. start() and close() would be similar to MessagePort. There is no need to close, but one can close it for performance reasons.

start() and close() can be called in whatever thread starts to use the object, similar to MessagePort. MessagePort.addEventListener("message", listener); does not call start() implicitly, but MessagePort.onmessage = listener; does. The implicit start() is weird and I don't think we want to expose all the event handler properties on EventPort.

* Is there a way of not transferring the ownership of the object?

Not sure I understand this question. One can always create a new EventPort.

* What do you think of also firing a new event type (like destroyed or whatever) on this (shadow) object when the real target (i.e. the one in the DOM) was destroyed and no longer was capable of receiving events?

Can't do that. It would reveal GC behavior.

* In the original proposal we had this idea that main thread could remove the delegating the target.

Call .stop() ? Or perhaps I misunderstand you comment.

NavidZ commented 5 years ago

Not sure I understand this question. One can always create a new EventPort.

My understanding is that when we pass an object (such as EventPort) as per spec the ownership of the object is transferred. So in this case main thread which creates the EventPort object (after calling the postMessage with that object) should not access any of its attributes including calling its close function. So that is what I meant by a way for not transferring the ownership. Aside from who can call the start/close function, if a worker wants to pass this very object to another worker then when it passes it through postmessge again it loses access to it. So either the shadow object should have a function to clone it so the worker clones it first and passes it or it should be copied (and not transferred) as oppose to what spec says.

Can't do that. It would reveal GC behavior.

Regarding the destruction, I didn't mean the GC destruction. I meant more in the line of when the object gets disconnected from the DOM and can no longer be targeted by any event and hence the shadow object can no longer receive an event.

Call .stop() ? Or perhaps I misunderstand you comment.

I'm referring to the fact that main thread doesn't have control on the EventPort object it sent over to the worker which I discussed more in the first paragraph of this comment.

smaug---- commented 5 years ago

So that is what I meant by a way for not transferring the ownership.

Ownership of EventPort is moved, but not ownership of the original EventTarget.

disconnected from the DOM and can no longer be targeted by any event

well, element may get connected back to DOM. Why should we close EventPort if one is moving an element in DOM? (move == remove from DOM tree + add back to DOM tree)

NavidZ commented 5 years ago

Ownership of EventPort is moved, but not ownership of the original EventTarget.

That's my point. So the creator of the EventPort (say main thread) cannot stop the events anymore for a particular EventPort it has created in the past. Right? We can say we just don't support that if we think there is no valid use case for such a thing but I wanted to call this difference out between this proposal and what is proposed now. Also how does the worker delegating to another worker work? We either have to have a clone function on EventPort so worker (which only gets an EventPort) could also copy it and send it off to another worker or it would lose the access of its own by sending the only EventPort reference it has. Right?

well, element may get connected back to DOM. Why should we close EventPort if one is moving an element in DOM? (move == remove from DOM tree + add back to DOM tree)

Fair enough. This extra feature is not really that important anyway IMHO.

smaug---- commented 5 years ago

That's my point. So the creator of the EventPort (say main thread) cannot stop the events anymore for a particular EventPort it has created in the past.

now I see what you mean. Right, there isn't way to do that. Main thread could always post a message to the relevant worker to ask it to call stop()

Also how does the worker delegating to another worker work?

So if you want to delegate the work, you just pass EventPort to the right worker. If you want to also keep handling the events you need to create a new EventPort and pass original EventPort as the eventtarget param or so.

Perhaps EventPort ctor needs some extra param to tell how to forward events - whether it eats all the events on the original target or whether it just clones the events.

NavidZ commented 5 years ago

now I see what you mean. Right, there isn't way to do that. Main thread could always post a message to the relevant worker to ask it to call stop()

I'm fine with not letting the originator of event delegation cut the event flow directly at this point. It is the same as removing the removeDelegate function from the current proposal. @mustaq @majido do you see any problem with that?

But I'd like to bring back the discussion we had earlier regarding stop and start and why we would need them. I still don't see why we need those functions instead of relying on the existence of listeners on the objects. For example in Blink browser knows whether there are listeners at all on the renderer (main thread) and if there is non it already skips the events. Note that listeners do include default handlers of elements if there is any. Why not just follow the same pattern of implicit forwarding if there is any listener? Do you know why we had that logic in the messagePort in the first place? Was is like a performance reason or ergonomics of the API? Although not having extra functions and just implicitly deciding based on the listeners seems more ergonomic to me.

So if you want to delegate the work, you just pass EventPort to the right worker. If you want to also keep handling the events you need to create a new EventPort and pass original EventPort as the eventtarget param or so.

But worker has only the EventPort and not the EventTarget. If we want to let the worker independent of the maim thread to do that then we need to have a clone function on the EventPort probably. But I guess we can work on that (i.e. maybe cloning on EventPort) for the future versions as well. WDYT?

Perhaps EventPort ctor needs some extra param to tell how to forward events - whether it eats all the events on the original target or whether it just clones the events.

I do like this idea and want to keep the dictionary option to add this feature possibly in the future ierations.

smaug---- commented 5 years ago

but I'd like to bring back the discussion we had earlier regarding stop and start and why we would need them. I still don't see why we need those functions instead of relying on the existence of listeners on the objects.

Because it is considered a bad API design. And MessagePort doesn't have such magical behavior on addEventListener either. (internally browsers of course optimize out dispatching various events if there are no listeners.)

NavidZ commented 5 years ago

Because it is considered a bad API design. And MessagePort doesn't have such magical behavior on addEventListener either. (internally browsers of course optimize out dispatching various events if there are no listeners.)

I'm just trying to draw parallels between main thread event listeners and worker side event listeners. If we were to mimic the same pattern of main thread listeners the workers should also just get the events if they have listeners rather than having another function to stop/start it. I believe having those controls on the worker side does make the usage of event handlers slightly different than the main thread side. Basically I'm trying to make the object main thread passes over to the worker to be more like an EventTarget and its listeners than a message port. WDYT?

smaug---- commented 5 years ago

I guess that works if we don't want to queue events, but if we do, similarly to MessagePort, then some kind of start() is needed.

NavidZ commented 5 years ago

I guess that works if we don't want to queue events, but if we do, similarly to MessagePort, then some kind of start() is needed.

Can you elaborate a little more one what you mean by "queueing the events". In my point of view we are already queueing the events (and tasks) in the event loop of the main thread. So I see the same setup for the workers in this case and nothing more.

smaug---- commented 5 years ago

MessagePort works so that events created by postMessage on the other port will get queued on the receiving side until start() is called. (well, technically tasks are queued) https://html.spec.whatwg.org/#dom-messageport-start

Similarly EvenPort could (if we want) to queue events dispatched to the original target while EventPort object itself is being transferred to whatever worker/worklet/window wants to use it.

NavidZ commented 5 years ago

I see now what you are saying. That was not at least our initial intention. Particularly because when the EventPort object is created we don't know what events this is interested in and then if we were to queue everything we need to send everything throughout the whole pipeline. Although I can see one can come up with a use case that might need that. Do you think it is important to address it at this stage?

What we had in mind was to just start sending the events when the listener was added by the worker and there is no need for further queueing anything in the EventPort when the object is created. The way I look at it is that we do not create an EventPort. We are actually creating a shadow target (not to be confused with Shadow DOM at all) of the DOM node and pass it to the worker. Then with respect to eventing it behaves the same the actual target on the main thread with respect to listeners.

smaug---- commented 5 years ago

Sure, but that is very racy. With queuing you can guarantee that the final target gets all the events.

NavidZ commented 4 years ago

Sure, but that is very racy. With queuing you can guarantee that the final target gets all the events.

I see. But this sort of raciness already exists today even on the main thread. For example the content might have been rendered but the event listeners haven't been added. So the user start interacting and they will not get feedback. I believe that might be negligible as that time is more of a startup time. But let's continue discussion on the pull request.