-
Notifications
You must be signed in to change notification settings - Fork 377
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
Custom Element definition ordering can cause difficult-to-find bugs. #668
Comments
I think it would be too risky make |
I'm still battling with this. This one gives true: connectedCallback() {
setTimeout(() => {
this.firstChild instanceof SomeCustomElement // TRUE
}, 0)
} but this one gives false: connectedCallback() {
Promise.resolve().then(() => {
this.firstChild instanceof SomeCustomElement // FALSE
})
} . The timing of element upgrades is too tricky to work with (f.e. #671). I can't reliably detect upgraded child elements in a microtask which means I can't reliably do some rendering in the next frame because if I use IMO such logic-deferral hacking should not ever be needed by a CE author for detecting children. In React, f.e., |
The problem in my last comment is because the elements are upgraded during DOM parsing, so an elements Making shareable reusable components currently involves tricky and difficult timing issues. Ideally it would be as easy as with React or Vue, so that anyone (especially newcomers to the web) can start making components without friction. |
Hmmm, but I thought parsing was synchronous? If it was, then it shouldn't make a difference if I was using |
Actually, with native CE v1 in Chrome, |
Alright, so if I wait for the document to be ready, it seems I can make more of my particular cases work: connectedCallback() {
documentReady().then(() => {
this.firstChild instanceof SomeCustomElement // TRUE
})
} but now I've introduced the ability for users of my elements to add/remove elements to/from DOM before document is ready, and who knows what bugs this could cause my elements. There's just too many unknown cases. ¯\_(ツ)_/¯ |
I really think making any parsing atomic as far as CE is concerned, and just general guarantee on CE life cycle ordering (in whole HTML trees, not just for individual elements, would lead to programs that are likely to be more reliable let alone easier to make. |
To depict the situation, the following example shows all the actual test cases that I'm testing (I comment them all out except for one at a time, for now). https://gist.github.com/trusktr/725acea2e839330400aaa791aa9fbbcc They're labeled as WORKS or BROKEN. I've got just a few broken ones left to fix. The ones that work show visual output like |
Much more of my cases were This shows the nature of CE timing complexity when elements need to be aware of their (upgraded) |
Lastly, for reference, my actual connectedCallback() {
//setTimeout(() => {
//Promise.resolve().then(() => {
documentReady().then(() => { // seems to work best
const children = this.childNodes
for (let l=children.length, i=0; i<l; i+=1) {
// do something with child of certain (upgraded) type
}
})
//}, 0)
} |
You really can't rely on your children being upgraded like that. HTML parser can add more children as it parses things, and there's no way to know when that happens. It could be before or after See #619 |
I don't think there's anything to be done in the spec here. |
There was according to https://bugs.chromium.org/p/chromium/issues/detail?id=658119#c4 a proposal for adding an "end tag parsed" callback to the spec. It seems to me that such a thing would clear this up. Let's call it /**
* Fires when the local DOM subtree has been fully parsed
* and, if we are lucky, all nested CEs are fully upgraded :)
*/
readyCallback() {
if(this.querySelector('form')) {
this.classList.add('interactive');
}
} Note that the example use case doesn't involve nested custom elements, just plain elements, because I think those are important to keep in mind. I haven't been far enough down this rabbit hole to tell if it would solve everything, because I abandoned my plan of basing our custom hacked framework on Custom Elements as soon as this issue became apparent. I think it is a real problem. It could also be fixed in another spec if the connectedCallback() {
this.addEventListener('DOMContentLoaded', () => {
if(this.querySelector('form')) {
this.classList.add('interactive');
}
});
} In any case, the spec does need to come up with a way for custom elements to synchronously evaluate the descendant subtree before it switches to Mutation Observers to monitor future updates, or at least it needs to formalize the use of |
Right. Upon reading #668 (comment) again, it sounds to me like the issue is actually fixed in the native implementation and that perhaps this appears worse than it really is because the polyfill uses Mutation Observers for some kind of asynchronous implementation? In that case, the solution is easy: Just wait for <ul>
<script>alert(document.querySelector('ul').innerHTML)</script>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul> This will alert then |
@rniwa If we don't add anything to the spec, then as @wiredearp suggested, many authors will need to similarly wrap their callback logic in Many people are going to do something like this, using jQuery: connectedCallback() {
$(document).ready(() => {
doSomethingWithUpgradedChildren(this.children)
})
}
I'd rather wait for the closing tag of an element, even if that means waiting for the network, and therefore for inner element constructors and connectedCallbacks to ALWAYS fire in the same order. This will simply make programs predictable and reliable. Just imagine the trouble React and Vue would cause if they ran life cycle methods in arbitrary ordering depending on this or that. It'd be a tsunami of issues flooding GitHub. Most people these days are not blocked by network on fetching DOM. At least, it's not a significant problem on any sites I ever visit, or the time it takes is always fast enough that I don't notice any problems. |
@rniwa My overall sentiment: consistency (even if slower from the network) would be much more appreciated by many devs (even if the appreciation is unknown to them because consistency won't introduce problems for them) over inconsistent behavior that will introduce bugs into many dev's custom elements before they realize it and have already shipped things to production. The current behavior is just unexpected land mines. Are you sure that you are okay with this? |
Either this is a duplicate of #619 or it's just a question. I don't think there's anything new we haven't thought of. I agree what you're trying to do is tricky but that's what #619 is for. If you'd like to see that API being spec'ed then we need to keep the discussion in that issue instead of creating new issues to discuss very similar use cases. |
…rocess, we fixed many cases of script load order issues, so now we can define elements in any order agnostic to when/how the elements are put into the DOM. This was painful, see WICG/webcomponents#668 for details on the type of stuff that needed to be dealt with. We also broke IE 11, and some demos in Edge, but we'll first refactor everything and get that working in Chrome then fix it up again in a following commit.
All my observations were based on Chrome's native implementation: initial DOM parse not being atomic (connetedCallbacks fire from parent to child, down-the-tree, children visible with deferral) while innerHTML being atomic (connectedCallbacks fire from child to parent, up-the-tree, children visible without deferral). |
What are the recommended ways of dealing with load order issues? For example, take these two fiddles:
true
false
One works (outputs
true
) and one doesn't (outputsfalse
) for reasons that might not be very obvious to people learning how to use web technology. New programmers shouldn't have to deal with these sorts of problems if they can be avoided by the internal implementation.A simple deferral of the constructing of Custom Elements to a future microtask would likely solve the problem without requiring Custom Element authors to write defensive deferral code themselves.
Note, Custom Element authors can not determine in which order users will load their scripts, and if elements will be in DOM or not before those scripts are loaded, and this causes unnecessary, increasingly complex, defensive code to be written.
Here's the same example, in the other two possible permutations:
false
false
And in those last two permutations, the ordering doesn't help at all! It's
false
both ways!Here's a defensive code example that always works, in all four permutations:
true
true
true
true
And, unfortunately, it only work in two of the four permutation when using microtasks! See:
true
true
false
false
The best solution in userland probably involves
customElements.whenDefined
, but it's still defensive coding.The HTML engine could, at least, guarantee that if these elements are defined within the same macrotask synchronously, then the result will be
true
in all permutations. This would make things easier for people, especially new people who are writing code and letting other mechanism place their codes into a page in arbitrary order that they aren't in control of.Would it be possible to fix this on the browser engine side by not upgrading elements until a microtask-like similar to how MutationObserver runs on a microtask-like?
The text was updated successfully, but these errors were encountered: