Re: [whatwg/dom] Atomic move operation for element reparenting & reordering (Issue #1255)

Hey all,

I want to weigh in with my perspective in support of both a low-level `moveBefore()`, and a higher-level version of `moveOrInsertBefore()` that has fallback behavior (but only in some cases). But first, I want to briefly discuss some meta-issues.

This thread is unfortunately noisy, full of long and repetitive comments by the same people. This is not helpful for making your point, and indeed is more likely to cause entrenchment and get yourself ignored. I usually subscribe to all whatwg/* issues, but had to unsubscribe from this thread (and the associated pull request) because of the excess noise a week ago. I see that since then there have been tens of comments. Please consider a more constructive strategy for engaging in standards bodies in the future, such as leaving a single, ideally short comment laying out your position and then accepting the fact that others might disagree. I'll call out @sorvell's single such comment at [https://github.com/whatwg/dom/issues/1255#issuecomment-2493840206](https://github.com/whatwg/dom/issues/1255#issuecomment-2493840206) as a good example of this.

Secondly, I think it's discouraging for us as spec writers and implementers to work on such a cool, technically-difficult, and long-awaited feature, which does the magic work of preserving iframe/canvas/etc. state even across moves… and have almost the entirety of the web developer feedback be about surface-level details. There's no appreciation or encouragement; there's just complaints about how terrible it is to write an extra `if` statement, often coupled with hyperbolic discussions about how it makes the API useless or footgunned or non-performant. Now, we aren't entitled to positive feedback! We're doing this as part of our jobs, after all. But please consider the human cost of how you're choosing to engage in these discussions.

Alright, on to the technical bits.

I don't think performance is a serious consideration here. `if` statements are not significantly faster in C++ vs. JavaScript. It would be best to set that aside.

I agree there's value in a low-level `moveBefore()` primitive that *only* moves. This gives clear semantics for low-level DOM manipulation, where you strongly intend move semantics, and you know it's a programmer error if you accidentally create code that crosses tree boundaries and thus is no longer able to do a move. The DOM is already full of such cases: e.g., we know it's a programmer error to try to insert an `Element` as a child of an `Attr`, and we don't fall back to inserting it into the `Attr`'s `ownerElement`. We throw an exception, so you can detect such coding errors quickly. Even for the current `moveBefore()` PR, nobody is claiming that there should be fallback behavior for moving ancestors to become their own grandchildren.

However, unlike the `Attr`-into-`Element` case, which is *always* a programmer error and falling back would never really make sense, I think there are *some* cases where you legitimately want to fall back to insert instead of move.

Recall that there are a few error cases under discussion:



* Disconnected-to-connected
* Connected-to-disconnected
* Cross-document

My proposal is that we allow `moveOrInsertBefore()` to fall back to `insertBefore()` behavior for the disconnected-to-connected case, but not the other two. Because disconnected-to-connected is the case where *you don't lose any state*. You can "gain" some state, by running the connected steps: e.g., your iframes will start loading. And that is signaled by the `OrInsert` part of the name.

I claim that the cross-document case is clearly a programmer error. Just like frameworks bubble up the exception that is thrown if you try to insert two `DocumentType` nodes into a `Document`, without catching it and adding fallback, they should bubble up the exception that is thrown if the web developer invokes some framework operation which moves elements across documents. Because otherwise, the web developer will silently and unpredictably lose state: iframes will be unloaded/reloaded, canvas contents will be cleared, form values will be reset, etc. And that state loss will only happen in the less-well-tested cases of cross-document operations.

Alternatively, frameworks could surface clearly-different APIs for same-document vs. cross-document operations, if they don't want to throw. This is not something they need to do today, because today there is no DOM API whose behavior differs significantly depending on cross- vs. same-document. But if a framework truly intends to be compatible with multiple documents, they're going to need to start surfacing this distinction to their user, because now such an operation exists. There's just no avoiding the extra complexity here. Hiding it is the wrong move, and not one we should encourage in the API design by having `moveOrInsertBefore()` silently fall back to `insertBefore()` in the cross-document case.

The connected-to-disconnected case is a bit trickier, but only a bit. I don't claim that it's always a programmer error to be moving an element into a disconnected document or `DocumentFragment`. But I do claim that, similar to the cross-document case, it needs to be handled consciously, not via automatic fallback, because of the possibility of lost state. Today, it's a transparent refactoring to start with code that moves from in-document point A to in-document point B, and rewrite it to code that moves from in-document point A, to an out-of-document DocumentFragment, to in-document point B. In both cases, you disconnect the element (e.g. unload the iframe), then reconnect it (reload it). But if you start using `moveOrInsertBefore()`, this is no longer a transparent refactoring: it's a state-losing bug. And we should surface such state-losing bugs loudly, via exceptions, and cause developers to actively handle such cases with extra code that makes their intent clear.

I think my proposal gives a version of `moveOrInsertBefore()` that frameworks and developers could slot in, in place of `insertBefore()`, which is basically just an "upgrade" to move behavior in all the cases that count. It still allows the relatively-common operation of assembling a DOM tree in a disconnected location before inserting into the document, via its `OrInsert` functionality. But it disallows the state-losing connected-to-disconnected and cross-document cases, which should be load failures by default, and the platform should not provide an easy escape hatch from.

I think we have ample evidence from this thread that this would be helpful to web developers today, and would be suitable for merging alongside the low-level `moveBefore()` primitive.

It's possible some web and framework developers feel that a state-losing fallback would be helpful, beyond what I've proposed. I am skeptical that we have enough evidence to support that conclusion. As per the above, I don't think frameworks have yet adapted to the reality where cross-document or connected-to-disconnected-to-connected are state-losing operations. I'd like to see how that goes for the next year or so before considering an even-more-lenient state-losing fallback version of this API.


-- 
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/dom/issues/1255#issuecomment-2507354378
You are receiving this because you are subscribed to this thread.

Message ID: <whatwg/dom/issues/1255/2507354378@github.com>

Received on Friday, 29 November 2024 08:56:49 UTC