Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Intent to Ship: DOM `moveBefore()` method, for state-preserving atomic move

1,495 views
Skip to first unread message

Dominic Farolino

unread,
Nov 14, 2024, 3:17:13 PM11/14/24
to blink-dev, Noam Rosenthal, Mason Freed

Contact emails

d...@chromium.orgnrose...@chromium.org

Explainer

Explainer

Specification

https://github.com/whatwg/dom/pull/1307

Summary

This feature adds a new DOM primitive (Node.prototype.moveBefore()) that allows moving connected elements around a DOM tree (same-Document), without resetting various pieces of element state. See this spec issue: https://github.com/whatwg/dom/issues/1255. The following state is ordinarily reset when an element moves in the DOM, but is preserved when the `moveBefore()` API is used:

  • Iframe documents (including unload events & document reloading)
  • Focus
  • Popovers
  • Modal dialogs
  • Fullscreen
  • CSS transitions & animations
  • Pointer events


Blink component

Blink>DOM

TAG review

https://github.com/w3ctag/design-reviews/issues/976

TAG review status

"Satisfied with concerns". TAG issue is resolved.

Risks


Interoperability and Compatibility

None


Gecko: Positive (https://github.com/mozilla/standards-positions/issues/1053)

WebKit: Support (https://github.com/WebKit/standards-positions/issues/375)

Web developers: Positive (https://github.com/whatwg/dom/issues/1255) See comments from HTMX (docs) & React, as well as Angular.

Other signals: N/A

WebView application risks

Does this intent deprecate or change behavior of existing APIs, such that it has potentially high risk for Android WebView-based applications?

None to speak of



Debuggability

None



Will this feature be supported on all six Blink platforms (Windows, Mac, Linux, ChromeOS, Android, and Android WebView)?

Yes

Is this feature fully tested by web-platform-tests?

Yes

See https://wpt.fyi/results/dom/nodes/moveBefore/tentative?label=experimental&label=master&aligned



Flag name on about://flags

AtomicMoveAPI

Finch feature name

None

Non-finch justification

This is a web platform API that should be made fully available in the milestone it is shipping with. The API is flagged, so we can kill it with Finch if needed.



Requires code in //chrome?

No

Measurement

A WebDX use-counter named "MoveBeforeAPI" has been added, and is referenced in the IDL for `moveBefore()`.

Adoption expectation

This feature will largely be used by framework authors when moving nodes around the DOM tree, and to a lesser extent, individual web developers specifically interested in this state preservation.

Adoption plan

Adoption is relatively easy. Developers can start using `Node#moveBefore()` in place of `Node#insertBefore()`, while accounting for the extra scenarios that `moveBefore()` will throw an exception in. In the error case, developers can consider delegating to `insertBefore()` instead. Furthermore, adoption is made easy by the fact that all cases under which `moveBefore()` would throw are web-observable & testable by developers.

Estimated milestones

Shipping on desktop133


Anticipated spec changes

Open questions about a feature may be a source of future web compat or interop issues. Please list open issues (e.g. links to known github issues in the project for the feature specification) whose resolution may introduce web compat/interop risk (e.g., changing to naming or structure of the API in a non-backward-compatible way).


During the spec review process, we decided (along with other browsers) to not preserve DOM live Range state, which includes text selection (see this spec review thread). The idea is that we'd revisit adding this if it becomes a serious developer want, but until then, the API will not preserve this state. We are hopeful that if we decide to add this preservation later, we can change the API to preserve this kind of state by default and still be web-compatible. However, the alternative would be to specify some options for the API to enable future kinds of preservation like Range/selection.

Link to entry on the Chrome Platform Status

https://chromestatus.com/feature/5135990159835136?gate=5177450351558656

This intent message was generated by Chrome Platform Status.

Yoav Weiss (@Shopify)

unread,
Nov 20, 2024, 10:38:04 AM11/20/24
to blink-dev, Dominic Farolino, Noam Rosenthal, Mason Freed
Thanks for working on this, this is exciting!!

On Thursday, November 14, 2024 at 9:17:13 PM UTC+1 Dominic Farolino wrote:

What's preventing the PR from landing?
 


Summary

This feature adds a new DOM primitive (Node.prototype.moveBefore()) that allows moving connected elements around a DOM tree (same-Document), without resetting various pieces of element state. See this spec issue: https://github.com/whatwg/dom/issues/1255. The following state is ordinarily reset when an element moves in the DOM, but is preserved when the `moveBefore()` API is used:

  • Iframe documents (including unload events & document reloading)
  • Focus
  • Popovers
  • Modal dialogs
  • Fullscreen
  • CSS transitions & animations
  • Pointer events


Blink componentBlink>DOM



TAG review status"Satisfied with concerns". TAG issue is resolved.

Risks

Interoperability and Compatibility

None


Gecko: Positive (https://github.com/mozilla/standards-positions/issues/1053)

WebKit: Support (https://github.com/WebKit/standards-positions/issues/375)

Web developers: Positive (https://github.com/whatwg/dom/issues/1255) See comments from HTMX (docs) & React, as well as Angular.

Other signals: N/A

WebView application risks

Does this intent deprecate or change behavior of existing APIs, such that it has potentially high risk for Android WebView-based applications?

None to speak of



Debuggability

None



Will this feature be supported on all six Blink platforms (Windows, Mac, Linux, ChromeOS, Android, and Android WebView)?Yes

Is this feature fully tested by web-platform-tests?Yes

See https://wpt.fyi/results/dom/nodes/moveBefore/tentative?label=experimental&label=master&aligned



Flag name on about://flagsAtomicMoveAPI

Finch feature nameNone

Non-finch justification

This is a web platform API that should be made fully available in the milestone it is shipping with. The API is flagged, so we can kill it with Finch if needed.



Requires code in //chrome?No

MeasurementA WebDX use-counter named "MoveBeforeAPI" has been added, and is referenced in the IDL for `moveBefore()`.

Adoption expectationThis feature will largely be used by framework authors when moving nodes around the DOM tree, and to a lesser extent, individual web developers specifically interested in this state preservation.

Adoption planAdoption is relatively easy. Developers can start using `Node#moveBefore()` in place of `Node#insertBefore()`, while accounting for the extra scenarios that `moveBefore()` will throw an exception in. In the error case, developers can consider delegating to `insertBefore()` instead. Furthermore, adoption is made easy by the fact that all cases under which `moveBefore()` would throw are web-observable & testable by developers.


Estimated milestonesShipping on desktop133

Anticipated spec changes

Open questions about a feature may be a source of future web compat or interop issues. Please list open issues (e.g. links to known github issues in the project for the feature specification) whose resolution may introduce web compat/interop risk (e.g., changing to naming or structure of the API in a non-backward-compatible way).


During the spec review process, we decided (along with other browsers) to not preserve DOM live Range state, which includes text selection (see this spec review thread). The idea is that we'd revisit adding this if it becomes a serious developer want, but until then, the API will not preserve this state. We are hopeful that if we decide to add this preservation later, we can change the API to preserve this kind of state by default and still be web-compatible. However, the alternative would be to specify some options for the API to enable future kinds of preservation like Range/selection.

Alex Russell

unread,
Nov 20, 2024, 11:32:40 AM11/20/24
to blink-dev, Yoav Weiss, Dominic Farolino, Noam Rosenthal, Mason Freed
This is very exciting, and I'm eager for this capability to ship, but I'm also worried that we're only doing one of the many append/insert API variants here. Is this the start of a larger package of additions? Or the only planned version?

Best,

Alex

Jeffrey Yasskin

unread,
Nov 20, 2024, 11:57:29 AM11/20/24
to Alex Russell, blink-dev, Yoav Weiss, Dominic Farolino, Noam Rosenthal, Mason Freed
The TAG feedback was a request to see if we could make this change to the other methods by default, instead of adding a parallel set. If that's web-compatible, we might not have to think about the larger package.

--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.
To view this discussion visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/d4ad2c0c-05a4-4d40-8d91-aebc043d4489n%40chromium.org.

Noam Rosenthal

unread,
Nov 20, 2024, 3:29:53 PM11/20/24
to Jeffrey Yasskin, Alex Russell, blink-dev, Yoav Weiss, Dominic Farolino, Mason Freed
On Wed, Nov 20, 2024 at 4:57 PM Jeffrey Yasskin <jyas...@chromium.org> wrote:
The TAG feedback was a request to see if we could make this change to the other methods by default, instead of adding a parallel set. If that's web-compatible, we might not have to think about the larger package.

TAG deferred back to WHATWG with the feedback.
There was a lengthy discussion about this at WHATWG before and as a result of the TAG feedback. The conclusion is that it's not feasible to change the behavior of all the existing methods, and changing the behavior of only some of them would be inconsistent and confusing.
There are only a couple of DOM methods that this applies to anyway (appendChild, replaceChild). The other ones (e.g. append(), before()) work on a series of elements so some of the assumptions that allow us to do this don't work there in the same way.
It's worth mentioning that the current direction was also a strong view from Gecko & WebKit folks.


On Wed, Nov 20, 2024 at 8:32 AM Alex Russell <sligh...@chromium.org> wrote:
This is very exciting, and I'm eager for this capability to ship, but I'm also worried that we're only doing one of the many append/insert API variants here. Is this the start of a larger package of additions? Or the only planned version?

All the other insertion methods can be polyfilled on top of insertBefore, so at least at first there could be a very small userland library that does this as "sugar". 
We can envision adding a move* variant to other methods in the future if there is demand, there is nothing preventing us from doing this.

Dominic Farolino

unread,
Nov 20, 2024, 3:55:08 PM11/20/24
to Noam Rosenthal, Jeffrey Yasskin, Alex Russell, blink-dev, Yoav Weiss, Mason Freed
What's preventing the PR from landing?

Just the fact that it hasn't been fully reviewed yet. We've received initial comments, but are waiting for a full round and LGTMs, and are confident they'll come soon.

There was a lengthy discussion about this at WHATWG before and as a result of the TAG feedback. The conclusion is that it's not feasible to change the behavior of all the existing methods, and changing the behavior of only some of them would be inconsistent and confusing.

To add to Noam's comments, a big deciding factor for the current direction was philosophical, and unrelated to web compatibility. Current editors (Anne and Domenic weighed in heavily during the TPAC 2024 discussion) felt strongly that the concept of "move" was conceptually a very distinct primitive from the existing "insert" and "remove" primitives. Therefore, usages of the existing primitives—through existing APIs—shouldn't suddenly mix the move behavior in with the current behavior, even if it were web compatible. The processing model of move compared to insert+removal is different enough that blending them is not what editors would have done even if "move" were conceived of from the beginning—this is the main feedback we got.

Jeffrey Yasskin

unread,
Nov 21, 2024, 3:23:14 PM11/21/24
to Dominic Farolino, Noam Rosenthal, Jeffrey Yasskin, Alex Russell, blink-dev, Yoav Weiss, Mason Freed
On Wed, Nov 20, 2024 at 12:55 PM Dominic Farolino <d...@chromium.org> wrote:
What's preventing the PR from landing?

Just the fact that it hasn't been fully reviewed yet. We've received initial comments, but are waiting for a full round and LGTMs, and are confident they'll come soon.

There was a lengthy discussion about this at WHATWG before and as a result of the TAG feedback. The conclusion is that it's not feasible to change the behavior of all the existing methods, and changing the behavior of only some of them would be inconsistent and confusing.

To add to Noam's comments, a big deciding factor for the current direction was philosophical, and unrelated to web compatibility. Current editors (Anne and Domenic weighed in heavily during the TPAC 2024 discussion) felt strongly that the concept of "move" was conceptually a very distinct primitive from the existing "insert" and "remove" primitives. Therefore, usages of the existing primitives—through existing APIs—shouldn't suddenly mix the move behavior in with the current behavior, even if it were web compatible. The processing model of move compared to insert+removal is different enough that blending them is not what editors would have done even if "move" were conceived of from the beginning—this is the main feedback we got.

Thanks for the pointer; I found https://github.com/whatwg/meta/issues/326#issuecomment-2377500295 recording that discussion. It looks like Anne's argument that we can't change the MutationRecords for the existing functions, is obsolete since https://github.com/whatwg/dom/pull/1307#issuecomment-2327504259 concluded we should use the existing remove+insert MutationRecords even for atomic moves.

And I intuitively agree with the argument that it's useful to have an operation (moveBefore) that guarantees it won't reset an element (it either moves atomically or throws and leaves the element in its original place), but a couple web developers just disagreed in https://github.com/whatwg/dom/pull/1307#issuecomment-2491906616.

The remaining arguments seem to be about 1) whether it makes sense for the movement APIs to have noticeably different side-effects when moving a node across documents and 2) whether the methods that reparent lists of elements can reasonably remove+insert some while atomically-moving others. My sense is that Lea Verou is right that it's better for authors if the existing APIs atomically-move the subset of their arguments that can be moved atomically, even if some of the arguments have to be removed+inserted. That said, the current behavior is to remove all the elements and then insert all of them, and I don't know how observable it would be to remove just the ones that can't be moved atomically, and then insert or move the list of elements into place.

All that said, the WHATWG forum has all the relevant experts, and if y'all think this is the right design after considering the arguments, I think you should go ahead.

Jeffrey

Dominic Farolino

unread,
Nov 21, 2024, 4:26:32 PM11/21/24
to Jeffrey Yasskin, Noam Rosenthal, Alex Russell, blink-dev, Yoav Weiss, Mason Freed
The remaining arguments seem to be about 1) whether it makes sense for the movement APIs to have noticeably different side-effects when moving a node across documents

Just to be super clear, we're not at all considering cross-document moves; our proposal is scoped to same-document only. Now, so I understand where you're coming from, you're saying that (1) above is about whether the side effects of (a) attempting a cross-document move (always an error) should or should not match the side effects of (b) moving a node that cannot be moved in a state-preserving way? I don't think whether these two "failure" cases have matching side effects is all that important, I just think that the side effects should make sense/be logical for each case.

2) whether the methods that reparent lists of elements can reasonably remove+insert some while atomically-moving others.

I honestly don't know if this is the most interesting part about changing the behavior of all DOM mutation APIs. Exactly what `append()` should do if some of its nodes can be atomically moved while others must be fully inserted is interesting, but I think the way more interesting question is should `append()` ever try and atomically move even just a single element that can be atomically moved? First, there is the web compatibility concern. But second, and more interesting to me, is predictability. It feels strange if sometimes `append()` has big side effects (script execution—which may attempt nested `append()` calls, iframes initializing, styles being applied, etc.), and other times no side effects at all. If you want to know whether `append()` will have side effects, then you have to find the spec'd conditions under which an atomic move will be performed and check these before `append()`. It's this predictability concern that I think motivated TPAC folks to lean towards `moveBefore()` throwing an error when an atomic move could not be performed.

The downside of course is that if you call `moveBefore()` from a generic place like a framework, and always want the node to just move from A->B (just atomically when possible), then you have to call `moveBefore()` in a try-catch and always call `insertBefore()` from the catch block. If 99% of `moveBefore()` uses are like that, then it becomes cumbersome. Anyways, I suppose we'll debate this exact thing over in the spec PR.

Jeffrey Yasskin

unread,
Nov 21, 2024, 4:44:05 PM11/21/24
to Dominic Farolino, Jeffrey Yasskin, Noam Rosenthal, Alex Russell, blink-dev, Yoav Weiss, Mason Freed
On Thu, Nov 21, 2024 at 1:26 PM Dominic Farolino <d...@chromium.org> wrote:
The remaining arguments seem to be about 1) whether it makes sense for the movement APIs to have noticeably different side-effects when moving a node across documents

Just to be super clear, we're not at all considering cross-document moves; our proposal is scoped to same-document only. Now, so I understand where you're coming from, you're saying that (1) above is about whether the side effects of (a) attempting a cross-document move (always an error) should or should not match the side effects of (b) moving a node that cannot be moved in a state-preserving way? I don't think whether these two "failure" cases have matching side effects is all that important, I just think that the side effects should make sense/be logical for each case.

Sorry for being unclear here. I was thinking of the question you elaborated on below, whether `append(elem)` should behave significantly differently (for certain kinds of input elements) depending on whether `elem` can be moved atomically. I agree that the details of the various failure cases for `moveBefore(elem)` don't need to be exactly consistent.

2) whether the methods that reparent lists of elements can reasonably remove+insert some while atomically-moving others.

I honestly don't know if this is the most interesting part about changing the behavior of all DOM mutation APIs. Exactly what `append()` should do if some of its nodes can be atomically moved while others must be fully inserted is interesting, but I think the way more interesting question is should `append()` ever try and atomically move even just a single element that can be atomically moved? First, there is the web compatibility concern. But second, and more interesting to me, is predictability. It feels strange if sometimes `append()` has big side effects (script execution—which may attempt nested `append()` calls, iframes initializing, styles being applied, etc.), and other times no side effects at all. If you want to know whether `append()` will have side effects, then you have to find the spec'd conditions under which an atomic move will be performed and check these before `append()`. It's this predictability concern that I think motivated TPAC folks to lean towards `moveBefore()` throwing an error when an atomic move could not be performed.

Doesn't `append(elem)` already sometimes have big side-effects (when `elem` is a <script> or <iframe>) and other times have no side-effects (when `elem` is a <b>)? It doesn't seem like reducing the number of side-effects is going to introduce a consistency problem here. I think the more important question is whether authors ever _want_ those side-effects. If they do, it's going to be web-incompatible to change the existing methods. If authors never want the side-effects, we should remove them whenever we can.

(This isn't an argument that moveBefore() should fall back to remove+insert. If the existing methods opportunistically use atomic-move, we can still have a new method that guarantees a lack of side-effects. And this consideration probably cuts the dependency between this I2S and a decision on whether to modify the existing methods.)

Jeffrey

Noam Rosenthal

unread,
Nov 21, 2024, 5:41:06 PM11/21/24
to Jeffrey Yasskin, Dominic Farolino, Alex Russell, blink-dev, Yoav Weiss, Mason Freed
On Thu, Nov 21, 2024 at 9:43 PM Jeffrey Yasskin <jyas...@chromium.org> wrote:
Doesn't `append(elem)` already sometimes have big side-effects (when `elem` is a <script> or <iframe>) and other times have no side-effects (when `elem` is a <b>)? It doesn't seem like reducing the number of side-effects is going to introduce a consistency problem here. I think the more important question is whether authors ever _want_ those side-effects. If they do, it's going to be web-incompatible to change the existing methods. If authors never want the side-effects, we should remove them whenever we can.

To add to this, we did try to prototype "changing the default behavior".

Adding move behavior to `append` or to any of the methods that take more than one element is a pandora's box, because currently all the removals occur before the insertions.
So splitting a moved element to an insertion and a removal, where the insertion is mixed with other insertions and the removal is mixed with other removals is a totally different model from what we went with, and is likely not doable.
In practice, this only leaves us with appendChild and replaceChild where we can probably have some plausible behavior, but probably a bigger backwards compatibility problem because they're older.
Having append() behave differently from appendChild() or having append() with more than one argument behave differently from append() with one argument is arguably very confusing, but we can consider doing this in the future.

In the meantime, moveBefore is a low level primitive - it only moves. It doesn't fall back - it fails if it doesn't do the one thing it's supposed to do. It's the first and lowest level version of atomic move - and it's plausible we can add more ergonomic higher level variants in the future.


Alex Russell

unread,
Nov 27, 2024, 11:16:05 AM11/27/24
to blink-dev, Noam Rosenthal, Dominic Farolino, Alex Russell, blink-dev, Yoav Weiss, Mason Freed, Jeffrey Yasskin
Thanks for all of this.

Would be good if we had a doc that explored the potential extension paths for the existing APIs (arguments object as additional param?) vs. the "larger bundle" path. I'm happy for this specific API to ship assuming we have those options written up for the TAG (and API OWNERS) to consider.

LGTM1 on the condition we produce that writeup.

Best,

Alex

Rick Byers

unread,
Nov 27, 2024, 11:35:32 AM11/27/24
to Alex Russell, blink-dev, Noam Rosenthal, Dominic Farolino, Yoav Weiss, Mason Freed, Jeffrey Yasskin
LGTM2 once the PR lands

--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.

Daniel Bratell

unread,
Nov 27, 2024, 11:42:26 AM11/27/24
to Rick Byers, Alex Russell, blink-dev, Noam Rosenthal, Dominic Farolino, Yoav Weiss, Mason Freed, Jeffrey Yasskin

LGTM3 to ship once the spec PR has landed.

Also, I'm really happy to see this new API coming to the web platform! Thanks to all that has made it happen!

/Daniel

Noam Rosenthal

unread,
Nov 28, 2024, 7:59:25 AM11/28/24
to Alex Russell, blink-dev, Dominic Farolino, Yoav Weiss, Mason Freed, Jeffrey Yasskin


On Wed, Nov 27, 2024 at 4:16 PM Alex Russell <sligh...@chromium.org> wrote:
>
> Thanks for all of this.
>
> Would be good if we had a doc that explored the potential extension paths for the existing APIs (arguments object as additional param?) vs. the "larger bundle" path. I'm happy for this specific API to ship assuming we have those options written up for the TAG (and API OWNERS) to consider.
>
> LGTM1 on the condition we produce that writeup.

Done, updated the explainer with detailed thoughts about this, in line as what was already said in this thread.
See specifically the "Considered alternatives" section, though the entire explainer was updated.

Rick Byers

unread,
Nov 28, 2024, 9:18:33 AM11/28/24
to Noam Rosenthal, Alex Russell, blink-dev, Dominic Farolino, Yoav Weiss, Mason Freed, Jeffrey Yasskin
Thanks Noam, this is great!

I agree with your argument against the TAG suggestion to consider reusing existing method names. With my web-compat expert hat on I can also say that changing the behavior of insertBefore to NOT reset state is almost certainly not web compatible. This is such longstanding and interoperable behavior that there are almost certainly going to be lots of websites which have accidentally come to depend on state being reset (eg. code which, after moving an iframe, waits for a load callback to reinitialize and activate it). To me it's debatable whether (absent any web compat risk) we'd even want to use the same method name for this different semantics. So IMHO it's certainly not worth the substantial effort, risk and web-developer pain to attempt a breaking change here. 
 

--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.
Reply all
Reply to author
Forward
0 new messages