input-for-workers
input-for-workers copied to clipboard
Consider to have EventPort
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.
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?
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();
}
I like a few properties of this proposal you are suggesting:
- We are sending the "event carrier object" (regardless of what we call it) through existing mechanisms (i.e. postMessage).
- It can be retransferred through the same mechanism to other threads.
A few notes:
- I think name wouldn't be necessary in this case. As the object is being sent with postMessage developers can tag along any other data they want with that message.
- I don't see how the start and close functions should be used in this context. I can see they are also in the MessagePort but what would they do in the eventing world? Can't we just leave this object around with no need to close it? I assume this object is transferred to the worker and these functions can only be called from that side. So worker can choose to let go of the only instance of the object so UA garbage collect the object and removes it. Also adding a listener of a particular type means it wants to get the events of that type. WDYT?
- Is there a way of not transferring the ownership of the object? The existing postMessage stuff says the object should be transferred and not cloned. In our use case here this object seems like an observer of the events for a given target. So it feels natural to be created of an EventTarget (like a DOM node) and then can be copied as well instead of transferred. That way a worker can let another sub worker to also listen to the events while itself keeps listening to those.
- 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?
- In the original proposal we had this idea that main thread could remove the delegating the target. Not sure how this can be done here. Although I don't object to not having that functionality with an explicit API. After all, all of these scripts are "controlled" by one developer and they should be able to communicate with other thread themselves if they desire to stop the forwarding by asking the other thread to stop listening to the events or drop the object instance altogether.
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.
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.
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)
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.
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.
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.
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.)
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?
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.
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.
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.
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.
Sure, but that is very racy. With queuing you can guarantee that the final target gets all the events.
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.