Skip to content
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

Introduce moveBefore() state-preserving atomic move API #1307

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

domfarolino
Copy link
Member

@domfarolino domfarolino commented Aug 26, 2024

Remaining tasks (some will be PRs in other standards):

  • Custom element integration
  • Keep popovers open
  • Don't call post-connection steps if state-preserving atomic move is in progress
  • Don't call becomes connected / becomes browsing-context
  • Only disconnect subframes on removal when state-preserving atomic move is not in progress
  • Preserve focus
  • Don't reset animations / transitions
  • Preserve text-selection
  • Preserve fullscreen

(See WHATWG Working Mode: Changes for more details.)


Preview | Diff

@domfarolino domfarolino changed the title Introduce \moveBefore()\ state-preserving atomic move API Introduce moveBefore() state-preserving atomic move API Aug 26, 2024
@domfarolino domfarolino added the impacts documentation Used by documentation communities, such as MDN, to track changes that impact documentation label Aug 26, 2024
Copy link
Member

@annevk annevk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the mutation record needs some more design work. I would expect it to capture the information of a remove and an insert at the same time. Perhaps it needs to be a new object, though we could further overload the existing MutationRecord as well I guess. At least I think you need:

  • old target
  • target
  • moved node (I'm not sure you can ever move multiple at this point, but maybe we should allow for it in the mutation record design?)
  • old previous sibling
  • old next sibling
  • previous sibling
  • next sibling

Would be good to know what @smaug---- thinks and maybe @ajklein even wants to chime in.

dom.bs Outdated Show resolved Hide resolved
dom.bs Outdated Show resolved Hide resolved
Comment on lines +5048 to +5049
<li><p>Let <var>return node</var> be the result of <a>pre-inserting</a> <var>node</var> into
<a>this</a> before <var>child</var>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be cleaner if we introduced "move" instead of overloading "insert", though I'm willing to be convinced. At least I always viewed this as introducing "move" as the third primitive following "insert" and "remove".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think initially I saw it as a separate primitive too, but the more I looked at it, the more the difference between the two seemed really subtle. It mostly has to do with MutationObservers (and half of the relevant logic here is tucked away in the "remove" primitive) and not running the post-connection steps. So I feel like we'd end up with a near line-by-line copy of "insert", modulo one or two small differences. I'll take another look to see if my intuition is accurate, but I do kinda suspect this is where we'd end up.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to have more details. Because you can't move a DocumentFragment so a vast number of checks don't apply. Adopt won't ever run.

There might be a number of range and shadow tree checks that end we end up duplicating, but perhaps that calls for abstracting those instead. At least to me a state flag seems rather unappealing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible there is enough from "insert" that we'd omit in "move", to make it creation worth it, sure.

At least to me a state flag seems rather unappealing.

With a separate "move" primitive and the decision to throw when we can't "move", we could get rid of the state variable that's currently in "insert" (i.e., <var>statePreservingAtomicMoveInProgress</var>). But the state bool on Document is what other specifications will refer to in their removal steps to react to a move appropriately, so I'm not sure we can get rid of that one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, wouldn't we give other specifications "move steps"?

<li><p>Let <var>perform state-preserving atomic move</var> be true if <a>this</a> is
<a>connected</a>, <var>node</var> is <a>connected</a>, <a>this</a>'s <a for=Node>node
document</a> is <a>fully active</a>, and <a>this</a>'s <a for=/>root</a> is the same as
<var>node</var>'s <a for=/>root</a>.</p></li>.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think this method should throw if the conditions aren't met. That seems much better for extensibility than implicit fallback to insert.

@annevk annevk added topic: nodes addition/proposal New features or enhancements labels Aug 27, 2024
@smaug----
Copy link
Collaborator

  • old target

  • target

  • moved node (I'm not sure you can ever move multiple at this point, but maybe we should allow for it in the mutation record design?)

Shouldn't the target node be all the time the same, it is just the siblings which change.
So we'd need only oldPreviousSibling and oldNextSibling. Oh, hmm, this isn't only about moving children but moving anything.

If this is really just remove and add back elsewhere, we could just reuse the existing childList MutationRecords, one for remove, one for adding node back, and possibly just add a flag to MutationRecord that it was about move.

(movedNodes is a bit confusing, since it seems to depend on the connectedness of the relevant nodes and it is apparently empty for the removal part. And it is unclear to me why we need the connectedness check. This is about basic DOM tree operations, and I'd assume those to work the same way whether or not the node is connected)

@annevk
Copy link
Member

annevk commented Sep 4, 2024

Creating two separate mutation records that a consumer would have to merge to (fully) understand it's a move seems suboptimal?

I agree that it should probably work for disconnected nodes as well, but I don't think we want to support a case where the shadow-including root changes.

@ajklein
Copy link

ajklein commented Sep 4, 2024

It's been a long time since I've thought about this stuff, but I'm inclined to agree with @smaug---- that creating a new type of MutationRecord feels unnecessary. Users of MutationObserver already have to do coalescing if they want to make sense of the stream of changes they observe. There are already other move-like operations, such as appending a child that's already somewhere else in the tree, which today generates two records.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements impacts documentation Used by documentation communities, such as MDN, to track changes that impact documentation topic: nodes
Development

Successfully merging this pull request may close these issues.

4 participants