-
Notifications
You must be signed in to change notification settings - Fork 297
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
Make it possible to observe connected-ness of a node #533
Comments
It wasn't clear from the original thread what the use cases were. @pemrouze just said it would be nice, then people started discussing how. We never even talked about why they couldn't use custom elements. I agree that if we had a need for this ability extending MutationObserver would be the way forward but I'd like some more justification in the form of concrete use cases that are hard to accomplish with custom elements. |
Agreed. It's always good to have a list of concrete use cases before adding new API. |
IIRC this was discussed around the time when MutationObserver was designed and the idea was that since one can add childList observer to the document and then just check document.contains(node), that should in principle be enough (and the things to observe was kept in minimum). |
This would be useful for polyfilling custom elements, but obviously that's not a good use case for why custom elements aren't enough. If I can page in my requests around MutationObservers one had to do with observing access shadow boundaries so we don't have to patch attachShadow, which might be relevant here, and have some way of unobserving nodes outside of disconnect(), say when the nodes are disconnected. |
|
Wouldn't a childList observer in document take care of those use cases, at least in common cases? |
It's unfortunately notoriously difficult to do with MutationObserver, and pretty much guaranteed to be buggy (due to it being batched) and inefficient (having to watch every change on the entire document). |
polyfills for custom elements already work and are either based on MutationObserver or DOMNodeInserted/DOMNodeRemoved with a lookup for registered elements in the list of changed nodes. Using polyfills is a weak reason, specially because anythig new needs to be polyfilled too. However I do have a use case in hyperHTML too but I believe React and many other frameworks would love this feature as well. CustomElements mechanism to understand their life-cycle are excellent but not everyone writes Custom Elements based sites and knowing a generic form, input, list element, grid card, has been added/removed would be ace. in hyperHTML I do have control of each vanilla DOM component but I need to do this "dance" per each MutationObserver notification: function dispatchTarget(node, isConnected, type, e) {
if (components.has(node)) {
node.dispatchEvent(e || (e = new Event(type)));
}
// this is where it gets a bit ugly
else {
for (var
nodes = node.children,
i = 0, length = nodes.length;
i < length; i++
) {
e = dispatchTarget(nodes[i], isConnected, type, e);
}
}
return e;
} The dispatching after recursive search is done per each |
@domenic any more thoughts? |
Here's a concrete use case. A editor library wants to know when the editor lost the focus. Because |
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as resolved.
This comment was marked as resolved.
Yes, this is much needed indeed. I have a concrete use case for progressive enhancement that is much inspired by the Custom Attributes proposal that was discussed a while back and I've brought into a user-land library. Custom Attributes are sort of like For example: // Listens for PointerEvents and paints Material Design Ripples on the host element
class Ripple {
constructor (hostElement) {...}
connectedCallback () {...}
disconnectedCallback () {...}
// ...
}
customAttributes.define("*ripple", Ripple); <!-- Custom Attributes are element-agnostic -->
<button *ripple>Click me</button>
<!-- They can also receive semi-colon separated key-value pairs as values, just like the style attribute -->
<my-element *ripple="center: true"></my-element> Now, while I would like to see Custom Attributes/mixins/behaviors standardized since To solve this today, using MutationObservers just doesn't cut it when using Shadow DOM. There may be many, changing, roots going up the tree from the node, or there may be none at all! You can observe child lists of all roots and hosts from the node and up the tree, but you have to do a lot of work to track changes to the parent chain and update the observers. But if the node has no parent by the time the MutationObserver needs to be configured, you will never know which root to observe. The "best" way to do this right now (as I see it) is monkey patching the |
I would like to revamp this conversation. It seems that we have more use-cases that can help us to come to an agreement about this feature request. Maybe adding it to the agenda for the F2F meeting. |
Yeah, I think we should just add this. Here's a concrete proposal: Add boolean Add two mutation record types
|
Sounds great. Wouldn't it be sufficient with a simple boolean |
Sorry, I wasn't thinking through. We could just add new mutation record types like |
@domenic The main use case I imagine is with builtins, so we can get connected/disconnected behavior that behaves the same as the callbacks for Custom Elements do. (I didn't see this thread before I wrote WICG/webcomponents#785) It'd be useful in some of the other issues I've had troubles with too.
@rniwa Another use case is simply: someone else made a web app, now we want to manipulate it without necessarily touching their source code (maybe we don't have access to the source, maybe we are writing a browser plugin, etc). There's still the issue of reaching into closed shadow trees though (it still requires patching attachShadow). In a sense the purpose of this feature is more similar to the purpose of jQuery: manipulate existing DOM elements with it. This is in contrast to the act of designing the behaviors of our own DOM elements (custom elements), instead we can use this to manipulate existing DOM elements (builtin or custom). 👍 to this. |
@caridy, I'm not sure if I believe a synchronous approach will be able to move forward since we had that with Mutation Events (which as you say doesn't work reliably with Shadow roots) and one of the primary reasons to move away from them in favor of MutationObserver was their synchronous nature. I'm certain you have perfectly valid reasons to want a synchronous solution, and I agree that this is an important missing piece in the web platform right now, but if we can find a way to move forward with an asynchronous approach and with an API that is similar to other related observers such as MutationObserver, I think the odds of getting implementors behind this is greater. I've experimented with this and created a library called ConnectionObserver which batches connection entries together in microtasks, and I'd like to propose something similar to that. |
@wessberg how is #533 (comment) solved by extensions to Having reread the thread it's not entirely clear to me everyone is on the same page here when it comes to use cases and requirements, which might also be why we keep hearing different solutions being proposed. Starting from the top at https://whatwg.org/faq#adding-new-features would be beneficial I think. |
@annevk, if However, alternatively, we could consider putting this functionality into an entirely new observer as described in my previous comment. This observer would observe a node, and be agnostic of whatever root it lives within. An example of using such API could be: // Hook up a new ConnectionObserver
const observer = new ConnectionObserver(entries => {
for (const {connected, target} of entries) {
console.log("target:", target);
console.log("connected:", connected);
}
});
// Observe 'someElement' for connectedness
observer.observe(someElement);
// Eventually disconnect the observer when you are done observing elements for connectedness
observer.disconnect(); More specifically: class ConnectionObserver {
[Symbol.toStringTag]: string;
/**
* Constructs a new ConnectionObserver
* @param {ConnectionCallback} callback
*/
constructor(callback: ConnectionCallback);
/**
* Observes the given target Node for connections/disconnections.
* @param {Node} target
*/
observe(target: Node): void;
/**
* Returns the entries that are currently queued in the batch and haven't been processed yet,
* leaving the connection queue empty. This may be useful if you want to immediately fetch all
* pending connection records immediately before disconnecting the observer, so that any
* pending changes can be processed.
* @return {ConnectionRecord[]}
*/
takeRecords(): ConnectionRecord[];
/**
* Disconnects the ConnectionObserver such that none of its callbacks will be invoked any longer
*/
disconnect(): void;
}
// A ConnectionCallback must be provided to the constructor of ConnectionObserver and will be invoked when there are new ConnectionRecords available.
type ConnectionCallback = (entries: ConnectionRecord[], observer: ConnectionObserver) => void;
// ConnectionCallbacks are invoked with an array of ConnectionRecords. Those have the following members:
interface ConnectionRecord {
/**
* Whether or not the node is Connected
*/
readonly connected: boolean;
/**
* The target Node
*/
readonly target: Node;
} That said, I think putting @annevk, I think you are right about starting fresh in regards to describing what problems we are trying to solve, and how they are related. I would say it boils down to generalizing the functionality enabled by the I'm against solving it by moving the two lifecycle callbacks into the Node interface for several reasons, including that they are synchronous and expensive. I'm in favor of an observer approach since multiple observers will be able to observe the same node, and most importantly, there are no performance impact for nodes for which there is no associated observer. |
@annevk I'm not proposing any particular solution, just trying to describe why we need this, and what are the options today that do not fulfill those use-cases. To be more specific about use-cases, I have one main user-story for the sync version of the connect-ness detection:
The use-cases are various, here are a few:
|
So by synchronous you mean equivalent timing to custom elements, not equivalent timing to mutation events? Why is mutation observer timing insufficient? Why would you need to polyfill/emulate custom elements at this point? |
For the same reasons node reactions are sync for CE callbacks. you want to know when the element is connected/disconnected to take immediate action, from registration, to coordinations with parent, to any other thing that all these libraries and frameworks are doing today synchronous as part of the diffing algo. Good example here is the snabbdom library that powers VUE and LWC, it is one of those popular diffing libs that provides tons of sync hooks for the operations on the dom.
Polyfilling is always handy, we don't know what the future will bring :), one example here is the scoped registry, that will take a while to take shape, but having this feature in place means that we can emulate some of the custom elements reactions without registering a real CE. For the record, we do that today in LWC, but it requires many hacks to control the connect-ness, so I might be bias. But in general, having a way to emulate these low level reactions seems like a good feature to have. |
@caridy, if we are going to pursue something that guarantees CE reaction timing for any given element, then I suggest opening a separate thread with such a proposal, because this thread so far has been centered around primarily I'm getting the feeling that this proposal is on the path to stagnation if we don't either:
|
@wessberg I'm fine with creating a different issue. The only reason why we keep debating this here is because in the last F2F meeting we discussed this topic (from the agenda), and the point of contention was exactly what you mentioned, sync vs async. As for the perf penalties, I will like to get more clarification from implementers. In theory, and by looking at the spec, the node reaction mechanism is in place for all elements, we only tap into them for custom elements today. The old mutation events are not necessarily a good reference, and they semantics are confusing, but certainly something to keep in mind during this discussions. @rniwa do you want me to open another issue for the sync proposal? |
Let's not open a new issue. We should figure out the best way to observe the connected-ness of a node in this issue. |
@rniwa alright. To avoid stalling, how about we investigate whether synchronous reactions are feasible first? @caridy, you say that you would like get feedback from implementors in regards to the perf implications of sync reactions. I think that is a great idea since it helps us understand if a sync solution is feasible. If implementors will simply block this proposal out of performance concerns and lessons learned from Mutation Events, that would be a shame. Is there anyone reading this who can chime in here or page someone who can? I think a way forward for us is to first understand what we can all agree on in regards to goals/non-goals. Is everone in agreement that:
|
hey implementers, any feedback on the implications of sync reactions? @wessberg as for your two bullet points, my assumption is that yes, we want to also observe nodes, and yes, we want multiple actors to observe the connectedness of the node at once. |
If I understand this correctly, connected-ness means if a Node appears in the document or not. If so, I'd like to share my use case. Now I have two choices, attach an event listener of deprecated |
Why can you not use a custom element? |
@Jack-Works I think this polyfill would solve your issue. Alternatively, element-notifier seems to address this exact issue too. |
This section in the README of your Your library works very similarly to the ConnectionObserver library that I built to address this, and it works across shadow roots, but it comes at the cost of patching It is unfortunate that this discussion hit a dead-end because we couldn't come to agreement about the whole sync vs async thing. I am still very much of the opinion that this thing has to be asynchronous and follow the same patterns as other existing observers, as a sync approach will most likely trigger performance concerns among implementers, just like MutationEvents did. |
Just want to add my use case: <template>
<p>My Custom Element with a menu</p>
<button onclick="toggleMenu">Toggle</button>
<x-menu>...</x-menu>
</template> That template gets used in a Custom Element where I also call a utility function that takes the custom element as its one param, searches all its children looking for any I'd like this utility function to also remove the listeners when the custom element is disconnected. If the utility class had a way to know when the custom element was disconnected that would be possible. Right now I have to return the collection of event registrations to the custom element and it runs through that list in its disconnectedCallback. |
Not sure if it has been suggested, but one way to make this plus a few more use cases easier would be to add an option to monitor parent changes. It's simpler than the original problem, and in some ways more general. |
It would be useful to have a way of observing when a node gets connected or disconnected from a document similar to the way
connectedCallback
anddisconnectedCallback
work for custom elements but unlike those at the end of the microtask like all other mutation records.The text was updated successfully, but these errors were encountered: