-
Notifications
You must be signed in to change notification settings - Fork 295
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
Allow custom "get the parent" algorithms for EventTargets #583
Comments
To what extent are you interested in also having shadow tree capabilities for your custom event targets? E.g., being able to hide event targets from |
@annevk I cannot think of a use case for shadow tree capabilities. If we are building tree structures out of these custom EventTargets solely to propagate events, having a capability of hiding/skipping certain nodes seems superfluous--one can just avoid putting those hidden nodes into the said tree structure. |
We are building web extensions which would like to implement events, to propagate critical occurrences from the browser api event listeners back to the extension business logic layer. We would want to leverage the EventTarget implementation to build propagation, rather doing it in an ad-hoc way. The ability to bubble the events is going to be a huge advantage given we can override/implement a custom 'get the parent' algorithm. |
I'm currently using a polyfill for But, for my polyfill, I'm creating a custom "get the parent" implementation via
The reason is I'm using a context => listener => client configuration for socket connections. Client connections themselves emit events which can be captured by the listener (TCP or WebSocket). Propagation can be stopped or it can continue bubble to the context (service that spawns the listeners). It makes sense because the application architecture is tree-like already. Unfortunately, if we decide to port to Edit: I just want to add that this continues to make more sense considering we can do |
+1 for this... In my case, it's just a whim. I'm currently writing a tiny widget's library where widgets are tree nodes (they have a parent and children), so I wanted to extend EventTarget and add event bubbling support, I stumbled upon this because I tried overriding dispatchEvent, so that: class ElementNode extends EventTarget {
constructor () {
super();
this.parent = null;
this.children = [ ];
}
append (nodes) {...}
remove (node) { ... }
walk (visitor) { ... }
dispatchEvent (event) {
super.dispatchEvent(event);
if (event.bubbles && this.parent) {
this.parent.dispatchEvent(event); // Losing event.target here :(
}
}
} The original If I were to think of the benefits I'd say that we would gain standardization... Allowing this customization can open the doors for other libraries or frameworks to rely on the EventTarget and CustomEvent APIs instead of everyone rolling their own "event emitter" solutions. |
Deno actually uses a somewhat custom method of "get the parent" that allows custom overloading. The check occurs by checking if the This is piggybacking off I can only realistically conclude that the EventTarget may be constructed with a |
Hm... we don't want to run arbitrary scripts just to get the parent. It needs to be some kind of setter/getter that UA would implement on |
@rniwa Do you think that having that setter/getter is a simple thing to do? And if so, what determines if that should be added to the spec? To be honest, I'm just curious |
Basically, you need to get at least two implementors interested in your proposal, write a DOM/HTML spec PR and WPT tests. |
My concerns with a getter/setter is a hard reference could leave an object in memory, prohibiting it from being garbage collection. With DOM nodes, once a child is detached from its parent, The other idea I had with my custom implementation is a EventTarget parent registry. Basically, instead of the child explicitly stating what it's parent is, a global registry can be used. eg: A pure JavaScript solution (polyfill) could look something like this: class EventTargetParentRegistry {
/** @type {WeakMap<EventTarget, WeakRef<EventTarget>>} */
static #registry = new WeakMap();
static get(child) {
return this.#registry.get(child)?.deref();
}
static set(parent, child) {
this.#registry.set(child, new WeakRef(parent));
}
static delete(child) {
this.#registry.delete(child);
}
} During the |
I'm not sure how that is consistent with the idea that EventTarget can have a parent. Is the idea that the event will propagate to the parent object only if that's still alive?? Literally nothing in the web platform works like that. I'm not certain we want to add API like this given its behavior will be highly dependent on a particular implementation of GC. |
In #1146 I suggested a method that could be available on
Having a method on the child event target allows it to perform necessary cleanup if called multiple times, and allows for customisation of the different phases. For example I can imagine some implementations opting out of the capturing phase and only allowing bubbling to occur. I am not precious about the implementation, but I'm happy to put the work in provided implementer interest. @rniwa is your commentary on this issue an indication of interest? Perhaps @mfreed7 or @domenic could also provide some insight on to interest within Chrome? |
It would help implementers (at least Mozilla) if there were a bit more concrete proposal here. It sounds like the use cases require effectively a DOM node tree, without the objects being actual nodes, but some other types of EventTargets. And one should be able to modify the tree using similar concepts as DOM tree. Minimal API from Event handling point of view would be something like setParentEventTarget(EventTarget? parent). |
An event only has a |
About two years later, I better understand @rniwa 's point about GC complexity. A class that extends If that's the right idea, let me know and I can write up a proposal draft. Something like class MyCustomEventTarget extends EventTarget {
/** @override */
get parentEventTarget() {
return AUTHOR_EVENTTARGET_WEAKMAP.get(this);
}
} IIRC, bubbling and capture are part of composedPath which shouldn't need any extra code other than a "get the parent" function/getter. |
@keithamus events always perform the capturing phase. Making that optional at dispatch somehow would be a distinct feature request we should track separately. @clshortfuse I don't think user agents should have to invoke JavaScript to traverse the tree. As @smaug---- suggests it should be possible for the user agent to have access to the entire path and impose constraints on it. |
Came here to file this exact same issue after naively trying to just set |
How about overloading the constructor of an [Exposed=*]
interface EventTarget {
constructor(parent?: EventTarget);
// ...
} If the |
I think that is probably a workable API (no need for null though, optional parent argument or dictionary with optional parent member seems fine), but we will also need to add loop detection in https://dom.spec.whatwg.org/#concept-event-dispatch. Perhaps that should lead to an exception? I think you can only ever reach it from |
Would something like c88f115 suffice? Should I raise this and work on some WPT/implementations? |
That's a start, but it'll need some more work to formalize it properly. (In particular you probably want to define some kind of internal field that holds the parent and that "get the parent" ends up returning. And the IDL syntax you're looking for is |
Do we want EventTargetInit in case we want more options in the future? Or would being an instance of @keithamus Thanks for working on this. I used init before and used a function instead, which is where we got to the garbage collection talk. We should be able to orphan and move those event targets, which means nulling and setting that value later during runtime. For my arbiter=>server=>client setup, orphaning is common. |
An init object could make sense. My understanding is that GC can dispose of the init object while retaining a reference to the parent, provided it is in the constructor. Getters can be retrieved during construction to capture the parent in an internal field. |
What about enabling This also means that we could even eventually port other DOM concepts to arbitrary objects with a tree-like structure (e.g. things like For objects that use another property to point to the parent object (e.g. get parentNode() {
return this.parent;
}
set parentNode (node) {
this.parent = node;
} Benefits:
Drawbacks:
Note that there is ample precedent around special property or method names doing special things rather than specifying properties in some init object, especally in Custom Elements. Another idea to avoid additional config could be to insert another subclass between That said, if any of this is entirely out of the question, it should definitely be an init object instead of a positional argument, see TAG principle 6.4 Accept optional and/or primitive arguments through dictionaries |
@LeaVerou What you suggest is how Deno already works. The code complexity around polyfilling Node is a quite a bit. I've tried it and ended up just wrapping EventTarget instead.
Looking at my code, I have a bastardization of Node as a shim for the tree, without all the document stuff. Subclassing between EventTarget and Node is probably better, though we'd have to define what goes in and what doesn't (eg: do we need to include The only "concern" I can see about passing an EventTarget instead ( class FooEventTarget extends EventTarget {
bubbles = false;
} Then if we make an |
Note that the design proposed here, of requiring an +1 to not conflating |
@LeaVerou the way custom elements handle this is very specific though to avoid crossing the C++/JS bridge too many times. Having to run a lot of script while building up an event's path isn't acceptable. It does seem like there are a number of use cases where you want to be able to manipulate the parent post-construction. That argues for some kind of revealing constructor pattern or something similar to [Exposed=*]
interface EventTargetInternals {
attribute EventTarget parent;
};
callback EventTargetCallback = undefined (EventTargetInternals internals);
partial interface EventTarget {
constructor(optional EventTargetCallback cb);
}; |
I don't understand the comments about mixing bubbles and EventTarget. And however we get the parent object, it needs to be very fast. Native implementation must not be required to touch any JS at that point (event dispatch is extremely performance critical). |
Tried to write up a spec around @annevk's IDL sketch of an |
I'd like to see that future. That is, the ability to create a tree structure for author-created EventTargets like the one we have with Nodes, with bubbling and capturing, and so forth. Two points in support of this:
An example use case:
Let's take the classic Todo app example. Imagine that each todo item is a custom EventTarget emitting "change" events, and the whole list is an array. Now, if we need to respond to any change in the list, say, recalculate the number of items, we have to add event listeners to each item. However, if we could define our array as the parent for each item where events could bubble to (or be captured on) the parent, we could get away with just one listener on the array. Since the items and the array already have strong coupling, adding an extra "parent" pointer wouldn't make difference in terms of the design.
The text was updated successfully, but these errors were encountered: