You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Allow custom "get the parent" algorithms for EventTargets #583
In the future we could allow custom get the parent algorithms. Let us know if this would be useful for your programs. For now, all author-created EventTargets do not participate in a tree structure.
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:
It would just make the platform more consistent when all EventTargets behave similar or have ways to do so. Right now we have a "weird" limitation to keep in mind when we work with custom EventTargets.
Listening just to a parent instead of each of its children simplifies our code when we work with Nodes, and same can apply to author-created EventTargets.
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.
changed the title [-]Allow custom "get the parent" algorithms for EventTargets.[/-][+]Allow custom "get the parent" algorithms for EventTargets[/+]on Mar 7, 2018
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 composedPath() and such. (This came up in #684.)
@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.
The shadow tree capabilities are not really necessary for author created event targets.
Reacted by Maga D. Zandaqo, rektide, Brian Kim, ssanabriacec, Andrew, Jorge Chayan and Dongook Lee
I'm currently using a polyfill for EventTarget in NodeJS environments. I see Deno will support EventTarget out of the box, so at least we're going to get nice usage from EventTarget.
But, for my polyfill, I'm creating a custom "get the parent" implementation via WeakMap as to not pollute the the property list.
getParent() gets checked and called during dispatchEvent(), much like how browsers do it. The problem is my custom implementations has to know to set privateEventTargetFields.get(eventTarget).getParent = callback;
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 Deno we will not be able to the official EventTarget implementation because spec has no "get the parent" function and no standard as to how to pass the "get the parent" function in the constructor.
Edit: I just want to add that this continues to make more sense considering we can do new EventTarget() now which I use for custom events that has nothing to do with the DOM. Also, some objects (eg: service workers, window, navigator) extend EventTarget but have little to no relation to the DOM (and workers can't even access the DOM), so I would say Events has branched outside the basic DOM scope already.
+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:
classElementNodeextendsEventTarget{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 event.target target is lost across calls because I'm manually dispatching the event in the parent, which then sets the target to be the parent. After reading the spec (https://dom.spec.whatwg.org/#concept-event-dispatch) I realized that the get the parent function can't be overridden and besides I can't just reassign event.target as it's read-only (and I understand why)
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 EventTarget "is a node" by checking if has a nodeType property. Then it uses .parentNode. That means an Javascript Object that has nodeType can potentially return a parent (via .parentNode).
This is piggybacking off Node, but to write a proper shim for it (I've tried it), you have to implement Node, ChildNode, ParentNode, Document. and some others that my escape me at the moment. That would be a lot of work for user agents to include and force them to require support something far more complicated just EventTarget. The Deno team is using something custom as is for lack of a standard, and I can totally understand doing it like that instead of writing all of Document.
I can only realistically conclude that the EventTarget may be constructed with a eventTargetInit object that may have a get the parent value. So you can maybe do something like new EventTarget({ getParent: myMasterParentFn }), for example. It keeps it strictly within EventTarget without telling user agents to support more complex types, or having developers need to add on something heavy like JSDom.
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 EventTarget interface.
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, .parentNode becomes null (or undefined, not sure). The parent can then be garbage collected if it's no longer referenced. That's one of the reasons why I leaned to a function.
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: parent = EventTargetParentRegistry.get(child). Here, a user agent can basically keep a dictionary of sorts since no child can have multiple parents.
A pure JavaScript solution (polyfill) could look something like this:
During the get the parent phase, the user agent (or polyfill) would use EventTargetParentRegistry.get(child) for EventTargets that are not Node objects.
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, .parentNode becomes null (or undefined, not sure). The parent can then be garbage collected if it's no longer referenced. That's one of the reasons why I leaned to a function.
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.
Reacted by Jorge Chayan, Darien Maillet Valentine and smaug----
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 optional EventTarget parent.)
Do we want EventTargetInit in case we want more options in the future? Or would being an instance of EventTarget be enough to create an init object later down the road, if ever?
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 parentNode to just work instead of adding more config? It should be fairly easy to investigate if this would be web compatible, but since bubbling is opt-in, I suspect it could be since there's no point in dispatching events that bubble on non-DOM nodes today, so even if objects have a parentNode property, it wouldn't do anything special.
This also means that we could even eventually port other DOM concepts to arbitrary objects with a tree-like structure (e.g. things like append() etc).
For objects that use another property to point to the parent object (e.g. parent), all this needs is
More flexibility for porting other DOM concepts in the future (see above)
Drawbacks:
Potential compat issue (needs research)
Less flexibility around object schema and parentNode is not a great name
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 EventTarget and Node, e.g. AbstractNode, for objects that have parentNode and other tree methods, but are not DOM nodes.
@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.
This is piggybacking off Node, but to write a proper shim for it (I've tried it), you have to implement Node, ChildNode, ParentNode, Document. and some others that my escape me at the moment. That would be a lot of work for user agents to include and force them to require support something far more complicated just EventTarget
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 insertBefore).
The only "concern" I can see about passing an EventTarget instead (new EventTarget(parentEventTarget)) of an init object is that EventTarget instances can be overloaded with their own properties, and I would want to avoid the possibilty of that custom EventTarget object later conflicting with an EventTargetInit property. For example:
Then if we make an EventTargetInit later with {bubbles: true, parent: foo} , it would be unclear if they're passing the parent or an EventTarget instance that has a property when doing new EventTarget(new FooEventTarget()).
Note that the design proposed here, of requiring an EventTarget parent object instead of a "function that returns an EventTarget" get-the-parent algorithm, means that this will only be usable when one is constructing one's tree of EventTargets in a top-down manner, where parents exist before children.
+1 to not conflating parentNode (part of the public Node interface) and "get the parent" (part of the EventTarget internal implementation).
@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 ElementInternals, e.g.,
I don't understand the comments about mixing bubbles and EventTarget.
Bubbling is a property of the Event, not EventTarget (and it just tell that the event dispatch does only capturing and target phases).
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).
Picking this back up, is there any reason this can't just be a writable property on event target itself? Or possibly a getter with a setter function (so it can throw if you try and do wonky things)?
Making it a constructor param for initial setting makes sense but I don't think it needs to be a callback to set it?
Element event targets can be reordered freely so doing that for event targets seems reasonable? Potentially a constructor flag to opt in if it's not a desirable default.
I think we should give them the same affordances as nodes have. If you want to give them a public interface you can, but we shouldn't make it impossible for them to have it be completely private.
Activity
[-]Allow custom "get the parent" algorithms for EventTargets.[/-][+]Allow custom "get the parent" algorithms for EventTargets[/+]annevk commentedon Sep 14, 2018
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
composedPath()and such. (This came up in #684.)zandaqo commentedon Sep 18, 2018
@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.
aayushch commentedon Jun 28, 2019
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.
The shadow tree capabilities are not really necessary for author created event targets.
clshortfuse commentedon Apr 25, 2020
I'm currently using a polyfill for
EventTargetin NodeJS environments. I seeDenowill supportEventTargetout of the box, so at least we're going to get nice usage fromEventTarget.But, for my polyfill, I'm creating a custom "get the parent" implementation via
WeakMapas to not pollute the the property list.getParent()gets checked and called duringdispatchEvent(), much like how browsers do it. The problem is my custom implementations has to know to setprivateEventTargetFields.get(eventTarget).getParent = callback;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
Denowe will not be able to the officialEventTargetimplementation because spec has no "get the parent" function and no standard as to how to pass the "get the parent" function in the constructor.Edit: I just want to add that this continues to make more sense considering we can do
new EventTarget()now which I use for custom events that has nothing to do with the DOM. Also, some objects (eg: service workers,window,navigator) extendEventTargetbut have little to no relation to the DOM (and workers can't even access the DOM), so I would sayEventshas branched outside the basic DOM scope already.jchayan commentedon Apr 5, 2021
+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:
The original
event.targettarget is lost across calls because I'm manually dispatching the event in the parent, which then sets the target to be the parent. After reading the spec (https://dom.spec.whatwg.org/#concept-event-dispatch) I realized that the get the parent function can't be overridden and besides I can't just reassignevent.targetas it's read-only (and I understand why)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.
clshortfuse commentedon Apr 6, 2021
Deno actually uses a somewhat custom method of "get the parent" that allows custom overloading. The check occurs by checking if the
EventTarget"is a node" by checking if has anodeTypeproperty. Then it uses.parentNode. That means an Javascript Object that hasnodeTypecan potentially return a parent (via.parentNode).https://github.com/denoland/deno/blob/704e1e53307130012da974f42d2cad94e07b5664/op_crates/web/02_event.js#L438
This is piggybacking off
Node, but to write a proper shim for it (I've tried it), you have to implementNode,ChildNode,ParentNode,Document. and some others that my escape me at the moment. That would be a lot of work for user agents to include and force them to require support something far more complicated justEventTarget. The Deno team is using something custom as is for lack of a standard, and I can totally understand doing it like that instead of writing all ofDocument.I can only realistically conclude that the EventTarget may be constructed with a
eventTargetInitobject that may have aget the parentvalue. So you can maybe do something likenew EventTarget({ getParent: myMasterParentFn }), for example. It keeps it strictly withinEventTargetwithout telling user agents to support more complex types, or having developers need to add on something heavy like JSDom.rniwa commentedon Apr 6, 2021
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
EventTargetinterface.jchayan commentedon Apr 8, 2021
@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
rniwa commentedon Apr 8, 2021
Basically, you need to get at least two implementors interested in your proposal, write a DOM/HTML spec PR and WPT tests.
clshortfuse commentedon Apr 9, 2021
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,
.parentNodebecomesnull(orundefined, not sure). The parent can then be garbage collected if it's no longer referenced. That's one of the reasons why I leaned to a function.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:
parent = EventTargetParentRegistry.get(child). Here, a user agent can basically keep a dictionary of sorts since no child can have multiple parents.A pure JavaScript solution (polyfill) could look something like this:
During the
get the parentphase, the user agent (or polyfill) would useEventTargetParentRegistry.get(child)for EventTargets that are notNodeobjects.rniwa commentedon Apr 9, 2021
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.
9 remaining items
annevk commentedon Oct 9, 2023
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
optional EventTarget parent.)clshortfuse commentedon Oct 9, 2023
Do we want EventTargetInit in case we want more options in the future? Or would being an instance of
EventTargetbe enough to create an init object later down the road, if ever?@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.
keithamus commentedon Oct 9, 2023
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.
LeaVerou commentedon Oct 9, 2023
What about enabling
parentNodeto just work instead of adding more config? It should be fairly easy to investigate if this would be web compatible, but since bubbling is opt-in, I suspect it could be since there's no point in dispatching events that bubble on non-DOM nodes today, so even if objects have aparentNodeproperty, it wouldn't do anything special.This also means that we could even eventually port other DOM concepts to arbitrary objects with a tree-like structure (e.g. things like
append()etc).For objects that use another property to point to the parent object (e.g.
parent), all this needs isBenefits:
Drawbacks:
parentNodeis not a great nameNote 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
EventTargetandNode, e.g.AbstractNode, for objects that haveparentNodeand other tree methods, but are not DOM nodes.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
clshortfuse commentedon Oct 9, 2023
@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.
#583 (comment)
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
insertBefore).The only "concern" I can see about passing an EventTarget instead (
new EventTarget(parentEventTarget)) of an init object is that EventTarget instances can be overloaded with their own properties, and I would want to avoid the possibilty of that custom EventTarget object later conflicting with anEventTargetInitproperty. For example:Then if we make an
EventTargetInitlater with{bubbles: true, parent: foo}, it would be unclear if they're passing the parent or an EventTarget instance that has a property when doingnew EventTarget(new FooEventTarget()).domenic commentedon Oct 10, 2023
Note that the design proposed here, of requiring an
EventTargetparent object instead of a "function that returns anEventTarget" get-the-parent algorithm, means that this will only be usable when one is constructing one's tree ofEventTargets in a top-down manner, where parents exist before children.+1 to not conflating
parentNode(part of the publicNodeinterface) and "get the parent" (part of theEventTargetinternal implementation).annevk commentedon Oct 10, 2023
@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
ElementInternals, e.g.,smaug---- commentedon Oct 10, 2023
I don't understand the comments about mixing bubbles and EventTarget.
Bubbling is a property of the Event, not EventTarget (and it just tell that the event dispatch does only capturing and target phases).
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).
keithamus commentedon Oct 10, 2023
Tried to write up a spec around @annevk's IDL sketch of an
EventTargetInternals: https://github.com/whatwg/dom/pull/1230/files. I'll noodle around with an implementation in the coming weeks.lukewarlow commentedon Jul 4, 2025
Picking this back up, is there any reason this can't just be a writable property on event target itself? Or possibly a getter with a setter function (so it can throw if you try and do wonky things)?
Making it a constructor param for initial setting makes sense but I don't think it needs to be a callback to set it?
Element event targets can be reordered freely so doing that for event targets seems reasonable? Potentially a constructor flag to opt in if it's not a desirable default.
domenic commentedon Jul 5, 2025
I think it'd be very bad if you were able to change the "event target parent" of a DOM node without actually changing its parent.
lukewarlow commentedon Jul 5, 2025
I agree to clarify I just meant for standalone event targets not associated with a node.
annevk commentedon Jul 5, 2025
I think we should give them the same affordances as nodes have. If you want to give them a public interface you can, but we shouldn't make it impossible for them to have it be completely private.
LeaVerou commentedon Jul 6, 2025
What about a new property then (
eventTargetParent?), that Node objects just alias toparentNode?annevk commentedon Jul 6, 2025
I don't see how that solves the problem.