-
Notifications
You must be signed in to change notification settings - Fork 378
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
Need a callback for when children changed or parser finished parsing children #809
Comments
Not sure if this is possible but would it be possible to simply capture the value of (This would be similar to the EDIT: It looks like the other methods already work like this by capturing the methods off the prototype (TIL). |
All custom element callbacks are retrieved at the time of definition so such an optimization is already possible. See step 10 of https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define |
I support adding a children changed callback as that is indeed a primitive the DOM Standard provides and specifications and implementations use. (Firefox also has internal synchronous mutation observers, but nothing in the platform relies on them, though Firefox does currently use them for the Before we add this however I'd like whatwg/dom#732 (review) solved. In particular, the primitive I referenced above isn't defined that well and some refactoring is needed. It'd be good to settle on the exact shape in private first before exposing it publicly. (Note that this is effectively #550 btw.) |
As with #550, I do not support this if it is parser-specific. If it is just a CEReactions-time MutationObserver that only custom elements can install, then I'm open to investigating. In that case I think we should also seriously consider un-deprecating |
I would love it to be a bit more than that, in that it observes projected children as well. I know that's covered |
I don't think observing the assigned slot should be conflated with this API. |
A common ask that keeps coming up is the ability to know when a child node has been inserted & updated: e.g. #765, #619, #615 To do this, just knowing when a child is inserted or removed isn't quite enough due to async / out-of-order upgrading. Perhaps we need both Also see whatwg/dom#662 |
@rniwa do you foresee this as something that can be observed in a custom element or a more low level API that can be applied to any element? It is not clear from the title or description. |
I think this needs to be a custom element reaction callback. |
That's probably limiting. I feel that this is in the same boat as the connect/disconnect reaction that we were asking for last week during the F2F, something a lot more generic. Now, if we were to add a new reaction callback, will that callback be invoked even when the CE is not connected to the DOM? e.g.: const ce = document.createElement('x-foo');
ce.appendChild(document.createElement('p')); |
For general nodes, you can just use MutationObserver. If you wanted it sooner, then that's a discussion that has to happen in whatwg/dom#533
You mean when it's not connected to a document? If so, then yes. There is no reason to restrict this |
Remember the imo much more important part remains the Also, for a |
Let first state that the time at which the parser had finished running isn't a great way to do anything because DOM is inherently dynamic. Any node can be inserted or removed after the parser had finished parsing its contents, not to mention that HTML parser could later insert more children via adoption agency, etc... The reason we want to add Now, custom elements have unique characteristics that child elements may be dynamically upgraded later so just knowing when a child is inserted may not be enough for a parent element to start interacting with a child element. This is a rather fundamental problem with the current custom elements API. We need some way for a parent to discover a child custom element when it becomes well defined. |
@rniwa how is |
I'm not interested in that case. Imagine you want to have an element In the course of the discussion back in the days I created a gist that sums up the problem and suggests a solution that served as a basis for the development of the aforementioned package. |
As already explained in multiple comments at the beginning of #551 that's not a pattern I want to encourage by adding API surface for it as it'll break down whenever someone dynamically uses such an element. I'm not sure why @rniwa mentioned the parser again. If we really want to reopen that discussion it should be in a separate issue as it's unlikely to get agreement, whereas children changed is an existing low-level primitive that would be vastly easier to get agreement on exposing. |
This is exactly what this issue addresses, unfortunately it mixes it up with
which is a completely different problem. I really think there is a strong necessity to solve the first problem first, because the second problem can already be tackled with using a if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
this.childrenAvailableCallback();
} else {
this.mutationObserver = new MutationObserver(() => {
if ([this, ...this.parentNodes].some(el=> el.nextSibling) || document.readyState !== 'loading') {
this.childrenAvailableCallback()
this.mutationObserver.disconnect()
}
}
this.mutationObserver.observe(this, {childList: true});
} does. Please note that this is also explicitly mentioned in the entry post:
|
I don't think parser finishing and dynamic children are different problems at all. The parser adding nodes is just a sub-problem of dynamic child changes and a single API can handle both cases. The fact that you can use MutationObservers after parsing is complete doesn't solve the current issue that developers have to piece together several APIs in order to know when their children change. |
Components that don't support updating its appearance upon dynamic DOM tree change is outside the scope of custom elements API. We don't intend to address such a use case. To answer @annevk's comment about why I brought up the parser again: I think there is legitimate scenarios in which the most natural solution authors think of is finishedParsingChildrenCallback. As far as I dissected the problem space, I think the most common complain there is that out-of-order upgrades makes it impossible to know when a parent can start interacting with children. I think this is an unique requirement that built-in elements don't have. So while I tend to agree we don't want to add that exact callback, we may need something like childDefinedCallback as I suggested above to address the underlying use cases of issues which motivated folks to request finished parsing callback. |
Can you please give reference on this? I assume such a heavy-weight decision is well-reasoned and -documented. What exactly is in scope of the custom elements API? |
@rniwa it seems to me that the combination of @franktopel it was decided early on that we wanted to encourage custom elements that behave equivalently to built-in elements and would therefore not add hooks that built-in elements do not have. It might be a little tricky to find a definitive reference for that, but that's best discussed separately from this thread in a new issue. |
To give some color about what I've been doing for this situation: I generally fire events from children when they're ready to interact with an ancestor, and have the child wait for the parent to be ready to receive the event with |
I think current work around is using MutationObserver on constructor, hence before the parsing is complete.
That works only if children are Custom Elements. A Imagine the server producing the following based on some data: <sortable-list>
<ul>
<li>a</li>
<li>b</li>
<ul>
</sortable-list> If you define upfront the I think @rniwa here nailed the issue me, and others, are complaining about.
Anything else would result in probably nice to have, but not an answer to the real issue. The fact built-in elements don't have such lifecycle is misleading, 'cause a We, users, simply don't get to know when that setup should happen, which is independent of the usage of MO in case the component should be able to react on added/removed nodes, 'cause these are not mutually exclusive scenarios. |
Did you test |
I don't think there's anything else to add in here ... this is close to the builtin extend discussion, where vendors can do it all, but exposed APIs to Web consumers are limited for no strong reasons. |
where vendors do it all for you, but the native API needs some extra work for consumers (replicating what vendor code does) |
@mfreed7 I would probably do it incrementally instead, that way it would be the same if the form came via the parser or a script (say, if the website was using the Navigation API). Any existing usage apart from maybe (It's also not clear to me why |
|
Heya, just wanted to chime in that I was building a simple component that only shows 1 of its children at a time using |
Coming back to this - I wonder if you could provide an example of how you'd "solve for that" here? Maybe using the two concrete use cases provided in this comment? One is for the |
XTF (which with XBL was like web components on steroids) had a notification to tell when all the child nodes had been added to it during parsing. And that notification was very useful to implement new elements. I don't yet have an opinion on whether some notification should come through MutationObserver or whether Custom Elements should have a callback. Children changed callback on a custom element might be rather slow if it was using CEReaction-type of timing during parsing - that would be basically the opposite why microtasks were created for MutationObserver. But then, on the other hand there is already attributeChangedCallback.... |
Thanks for the reference!
One point is that there seem to be use cases that don't involve custom elements, and it'd be nice to be able to solve those. So if there's a general solution (which I think MutationObserver represents), that would seem better to me. An async event would also do the trick. |
@mfreed7 I don't really understand the If it's for rendering I'd expect you maybe delay a bit and then just render what you have at some interval. Similar to incremental rendering in browsers. @smaug---- how was XTF resilient against imperative creation of the same elements? |
On a side note, the argument of "What if the end tag is never enountered"... well then you have bigger problems, and code that depends on reaching the end does not fire. This does not seems like a realistic counter argument. To render HTML you need the HTML, that much is a given. And secondly not wishing to provide it because could result in slower web pages, is like a country banning fire because people could get burnt. There are many ways to make a slow website. To not allow a feature that would help a lot just because it could be used the wrong way is tying developers hands and treating them like children. |
The use case (one of them, at least) is waiting for I'm not sure we have a list of the use cases the web is "designed for". We provide functionality, and creative developers build cool things with that functionality. Parsing |
JavaScript is not alone in the quirk of requiring the last byte to properly evaluate the file, many languages have references such as Yaml, Markdown, JSON Schema, consequently while |
There were begin/end callbacks for the parser creation. In the current element implementations in Gecko there is usually a flag which is set to true for non-parser created elements, and on parser created elements it is set to true when child nodes have been added to it. |
It would be nice if a solution here also addressed #979 If a child-parsed callback was only called by parser created elements, then script-like elements could only execute on that callback. Addressing the "file"-like data use case seems like a superset of executable data though, where you may was to know when data is complete in insecure parsing contexts like innerHTML. |
To me it seems to reliably access children in connectedCallback() {
if (this.children.length)
// access this.children
} else {
const observer = new MutationObserver(() => {
observer.disconnect();
// access this.children
}).observe(this, {childList: true});
}
} Honstly, it seems way to much boilerplate for such a simple and common use case. |
Just to clarify @silverwind it looks like your code is only for change events, and even then, only on the first change and doesn't cover the use case of indicating when the parser has completed parsing and appending children of a particular element on the DOM. |
Yes, in this limited case I'm only interested in getting the reference to a mandatory child element. It will not be removed/added again ever after. |
Another use case (for builtin elements) is source selection for |
Say a component has many children, is there any chance the parser may connect the component after parsing some of the children, i.e. not all of them. It appears to this code assumes that all the children exist, or none of them at the time the |
if you define your component at the end of the page you are 100% sure you can access its children and everything else as it gets just "promoted" as CE ... if you define your component AOT (which is 90% of what libraries do instead of lazy dafining their components only when needed/encountered) you are 100% sure (last time I've checked) the The whole issue is to have something that never fails and that can be used in all circumstances and definition time, something that while streaming a document will signal via The problem raised in here is that in some streaming scenario the browser might already consider the element closed ... add people with "smart suggestions" like "don't need to close a |
I wonder if simply using the I suppose it helps if the Component has not been registered already, but it might not help if it already registered. Couldn't components have a similar |
Often you have components that need to be loaded quickly, e.g. if they are above the fold. Putting them last on the page does not work in this case. It's kind of like putting your stylesheets just your closing For now I make a function that handles slotted children, that function has to be idempotent.
This handles the situation when the children are added before the component can initialise, and when they are slotted after it has been initialised. A bit clunky but don't know of any better. |
Updating on my case of streaming into a detached document and selectively moving nodes to the visible document. I found a much more efficient solution that also reliably identifies the closing tag by skipping the MutationObserver altogether and using a NodeIterator instead. Something similar might be possible for other use cases. Rough solution: // Only works when streaming into an empty detached document. Otherwise additional logic is needed.
function elementIsClosed(element) {
let currentElement = element;
while (currentElement) {
if (currentElement.nextSibling) return true;
currentElement = currentElement.parentElement;
}
return false;
}
const iterator = new NodeIterator(detachedDocument, NodeFilter.SHOW_ELEMENT);
let element;
const response = await fetch("https://example.com");
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
while (true) {
const { done, value } = await reader.read();
if (done) return;
detachedDocument.write(value);
element = iterator.nextNode();
while (element) {
const isClosed = elementIsClosed(element);
// Perform processing
element = iterator.nextNode();
}
}
detachedDocument.close();
// Process the root element if needed, document is closed so the root element is closed This works for a few reasons:
In some simple tests, a slower device can iterate a ~5000 node document in ~10ms with some basic conditional checks like attribute lookups. |
@nickcoury it's a great solution and thank sfor sharing but this also solves nothing and it's fully unrelated with Web Components, right? |
Sorry, yes it's not directly related but still may be relevant. See my earlier comments for context. I originally commented with a similar use case looking for a MutationObserver close tag event. I haven't fully explored this for web components but it's possible part of the approach could be adapted. I don't think there's an event when each chunk is written to check for completeness, though a new MutationObserver callback or a setInterval could be used. |
There appears to be a strong desire for having some callback when the parser finishes parsing children or when a new child is inserted or an old child is removed.
Previously, children changed callback was deemed too expensive but I'm not certain that would necessarily be the case given all major browser engines (Blink, Gecko, and WebKit) have builtin mechanism to listen to children changes.
I think we should consider adding this callback once for all. As much as I'd like to make sure custom elements people write are good, developer ergonomics here is quite terrible, and people are resorting to very awkward workarounds like attaching mutation observers everywhere, which is arguably far slower.
The text was updated successfully, but these errors were encountered: