- From: Dominic Farolino <notifications@github.com>
- Date: Wed, 14 Feb 2024 11:00:57 -0800
- To: whatwg/dom <dom@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <whatwg/dom/issues/1255@github.com>
### What problem are you trying to solve? Chrome (@domfarolino, @noamr, @mfreed7) is interested in pursuing the addition of an atomic move primitive in the DOM Standard. This would allow an element to be re-parented or re-ordered _without_ today's side effects of first being removed and then inserted. Here are all of the prior issues/PRs I could find related to this problem space: - https://github.com/whatwg/html/issues/5484 - https://github.com/whatwg/dom/issues/808 - https://github.com/whatwg/dom/issues/880 - https://github.com/whatwg/dom/issues/891 - https://github.com/whatwg/dom/pull/732 - https://github.com/whatwg/html/pull/4354 - https://github.com/web-platform-tests/wpt/pull/44308 - https://github.com/web-platform-tests/wpt/pull/15264 **Problem** Without an atomic move operation, re-parenting or re-ordering elements involves [first removing them](https://dom.spec.whatwg.org/#concept-node-adopt:~:text=node%20document.-,If%20node%E2%80%99s%20parent%20is%20non%2Dnull%2C%20then%20remove%20node,-.) and then re-inserting them. With the DOM Standard's current removal/insertion model, this resets lots of state on various elements, including iframe document state, selection/focus on `<input>`s, and more. See @jarhar's [reparenting demo](https://reparent.jarhar.com/) for a more exhaustive list of state that gets reset. This causes lots of developer pain, as recently voiced [on X](https://twitter.com/nomsternom/status/1755275696434286831) by frameworks [like HTMX](https://twitter.com/htmx_org/status/1755280639962591704), and other companies such as Wix, Microsoft, and internally at Google. This state-resetting is in part caused by the DOM Standard's current insertion & removal model. While [well-defined](https://github.com/whatwg/dom/issues/808#issuecomment-1913887358), its model of [insertion](https://dom.spec.whatwg.org/#ref-for-concept-node-insert-ext) and [removal](https://dom.spec.whatwg.org/#ref-for-concept-node-remove-ext%E2%91%A0) steps has two issues, both captured by https://github.com/whatwg/dom/issues/808: 1. **Undesirable model**: The current DOM Standard allows for the non-atomic insertion of multiple nodes at a time. In practice, this means when appending e.g., a DocumentFragment, script can run in between each individual child insertion, thus observing DOM state before the entire fragment insertion is complete. 2. **Interop issues**: While Safari matches the spec, Chromium & Gecko have a model that ensures all DOM mutations are synchronously performed before any script runs as a result of the mutations. ### What solutions exist today? One very limited partial solution that does not actually involve any DOM tree manipulation, is this shadow DOM example that @emilio had posted a while back: https://github.com/whatwg/html/issues/5484#issuecomment-620481794 (see my brief recreation of it below). ![Screen Recording 2024-01-29 at 5 00 26 PM](https://github.com/whatwg/dom/assets/9669289/9674f532-c0fb-4f46-ae01-cb63c4cd5ed9) But as mentioned, this does not seem to perform any real DOM mutations; rather, the slot mutation seems to just visually compose the element in the right place. Throughout this example, the iframe's actual [parent](https://dom.spec.whatwg.org/#concept-tree-parent) does not change. ---- Otherwise, we know there is some historical precedent for trying to solve this problem with WebKit's since-rolled-back "magic iframes". See https://github.com/whatwg/html/issues/5484#issuecomment-619642936 and https://bugs.webkit.org/show_bug.cgi?id=13574#c12. We believe that the concerns from that old approach can be ameliorated by: - Fixing #808, as to not leave elements in a disconnected state _at all_ during the atomic portion of the move (i.e., the re-parenting/re-ordering) - Not allowing atomic moves _across documents_, which should greatly simplify the security story of this work ### How would you solve it? **Solution** To lay the groundwork for an atomic move primitive in the DOM Standard, we plan on resolving https://github.com/whatwg/dom/issues/808 by introducing a model desired by @annevk, @domfarolino, @noamr, and @mfreed7, that resembles Gecko & Chromium's model of handling all script-executing insertion/removal side-effects _after_ all DOM mutations are done, for any given insertion. With this in place, we believe it will be much easier to separate out the cases where we can simply _**skip**_ the invocation of insertion/removal side-effects for nodes that are atomically moved in the DOM. This will make us, and implementers, confident that there won't be any way to observe an inconsistent DOM state while atomically moving an element, or experience other nasty unknown side-effects. The API shape for this new primitive is an open question. Below are a few ideas: - **A new DOM API** like replaceChildAtomic()/replaceChildrenAtomic() that can take a [connected](https://dom.spec.whatwg.org/#connected) node and atomically re-parent it without removal/insertion side-effects. - One limitation here is that we'd have to pick and choose which existing DOM APIs we want to mirror with atomic counterparts. For example, if we ever wanted append() or appendChild() to ever be able to _also_ atomically move already-connected nodes, we'd have to introduce appendAtomic() and appendChildAtomic(), and so on. - A **setting for existing DOM APIs**, e.g., `append(node, {atomic: true})`, `replaceChild(node, {atomic: true})` - A **scoped, declarative attribute** that changes the behavior of DOM mutation APIs in a subtree - This could be an element attribute that makes all existing DOM mutation APIs behave "atomically" when operating on already-connected nodes under the element's subtree - This could also be a property on the document overall, set via a header/meta tag, or some other mechanism ---- Compatibility issues here take the form relying on insertion/removal side-effects which no longer happen during an atomic move. They vary depending on the shape of our final design. 1. With a new DOM API/setting that developers have to affirmatively opt-into, you could atomically move fragments/subtrees constructed by _other library code that's unaware it's being atomically moved_. Those fragments may be built in a way that relies on non-atomic move side-effects (though we haven't heard of such concerns directly yet). 1. Consider an element attribute that changes the behavior of all DOM mutation APIs to behave atomically on already-connected nodes in its subtree. You could minimize compat concerns by externally-constructed portions of the subtree to opt-_out_ of atomic moves with the same attribute. But what would that mean exactly, to have part of a subtree move atomically and part of it not? A non-exhaustive list of additional complexities that would be nice to track/discuss before a formal design: - How to handle mutation events? There was [discussion at the TPAC 2023](https://github.com/whatwg/meta/issues/284#:~:text=Domenic%3A%20If%20all%20else%20fails%2C%20we%20could%20say%20that%20mutation%20events%20don%27t%20fire%20if%20any%20new%20feature%20is%20used) about suppressing mutation events when new-ish DOM features are used, so we could probably get away with simply suppressing mutation events whenever an atomic move is being performed?? - Handling things like focus/selection properly (need to land on desired behavior) - Fixing up things like [live ranges](https://dom.spec.whatwg.org/#concept-live-range); the way DOM handles this today might already be suitable for atomic moves, but unclear ### Anything else? _No response_ -- Reply to this email directly or view it on GitHub: https://github.com/whatwg/dom/issues/1255 You are receiving this because you are subscribed to this thread. Message ID: <whatwg/dom/issues/1255@github.com>
Received on Wednesday, 14 February 2024 19:01:09 UTC