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

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Changes from 26 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d45ca59
Initial atomic move skeleton
domfarolino Aug 19, 2024
98a70d5
Most of the `MutationObserver` integration
domfarolino Aug 26, 2024
93c2a53
Remove mutation events flag references
domfarolino Aug 26, 2024
1b2ec1c
Populate the `MutationRecord`
domfarolino Aug 26, 2024
30ded0c
Fix punctuation
domfarolino Aug 26, 2024
2811119
Remove trailing whitespace
domfarolino Aug 26, 2024
c659978
`<span>` -> `<a>` plus fix punctuation error
domfarolino Aug 27, 2024
ad05126
Camel case
domfarolino Aug 27, 2024
fbbde69
Throw an exception on failure
domfarolino Sep 24, 2024
b7b71bc
Introduce move primitive + moving steps hook/extension
domfarolino Sep 30, 2024
487a925
Remove changes to insertion primitive
domfarolino Oct 10, 2024
5127374
Revert document flag and mutation record changes
domfarolino Oct 10, 2024
2f132c1
Fix mutation record callsites
domfarolino Oct 10, 2024
f254047
Fix wrapping
domfarolino Oct 10, 2024
fe139ab
Remove suppress observers flag
domfarolino Oct 10, 2024
43578ce
Custom element integration
domfarolino Oct 10, 2024
1d4bb01
Update live ranges and NodeIterators properly; do not call the remove…
domfarolino Oct 10, 2024
696e0fe
Correct removal bookkeeping
domfarolino Oct 10, 2024
a9d54c5
Moving steps prose
domfarolino Oct 15, 2024
caab6e1
Tighten up pre-move checks
domfarolino Oct 15, 2024
a9bd9a8
Assert -> throw condition
domfarolino Oct 15, 2024
9ecae6f
Move conditions into pre-move validity
domfarolino Oct 15, 2024
35a75be
Fix `<old>`
domfarolino Oct 15, 2024
906d710
Ordering and format
domfarolino Oct 16, 2024
8d24d3b
Fix live range updating logic
domfarolino Nov 7, 2024
8b111ad
Document `move` primitive in Range note
domfarolino Nov 7, 2024
6572970
Enable moves in a connected `ShadowRoot` `DocumentFragment` node
domfarolino Nov 11, 2024
12b9ded
Do not explicitly rethrow
domfarolino Nov 13, 2024
29e2ac3
Do not do special range handling
domfarolino Nov 19, 2024
2bbc834
Support both connected->connected and disconnected->disconnected
domfarolino Nov 20, 2024
03d5b58
Whitespace and formatting
domfarolino Nov 20, 2024
a271c8c
Factor our live range pre-removal steps
domfarolino Nov 27, 2024
230423d
newParent, newPreviousSibling, and count
domfarolino Nov 27, 2024
36d78b1
Only queue connectedMoveCallback if connected
domfarolino Nov 27, 2024
fb64c3a
Revert random editorial change
domfarolino Nov 27, 2024
aead1cf
Remove newline
domfarolino Nov 27, 2024
ca80383
Remove manual custom element upgrade
domfarolino Nov 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 179 additions & 4 deletions dom.bs
Original file line number Diff line number Diff line change
Expand Up @@ -2652,6 +2652,50 @@ of a <var>node</var> into a <var>parent</var> before a <var>child</var>, run the
<!-- Technically this is post-insert. -->
</ol>

<p>To <dfn export for=Node id=concept-node-ensure-pre-move-validity>ensure pre-move validity</dfn>
of a <var>node</var> into a <var>parent</var> before a <var>child</var>, run these steps:

<ol>
<li><p>If either <var>parent</var> or <var>node</var> are not <a>connected</a>, then
<a>throw</a> a "{{HierarchyRequestError!!exception}}" {{DOMException}}.</p></li>
domfarolino marked this conversation as resolved.
Show resolved Hide resolved

<li><p>If <var>parent</var>'s <a for=/>shadow-including root</a> is not the same as
<var>node</var>'s <a for=/>shadow-including root</a>, then <a>throw</a> a
"{{HierarchyRequestError!!exception}}" {{DOMException}}.</p></li>

<li><p>If <var>parent</var> is not an {{Element}} <a for=/>node</a>, then <a>throw</a> a
"{{HierarchyRequestError!!exception}}" {{DOMException}}.
domfarolino marked this conversation as resolved.
Show resolved Hide resolved

<li><p>If <var>node</var> is a <a>host-including inclusive ancestor</a> of <var>parent</var>, then
<a>throw</a> a "{{HierarchyRequestError!!exception}}" {{DOMException}}.

<li><p>If <var>node</var> is not an {{Element}} or a {{CharacterData}} <a for=/>node</a>, then
<a>throw</a> a "{{HierarchyRequestError!!exception}}" {{DOMException}}.</p></li>

<li><p>If <var>child</var> is non-null and its <a for=tree>parent</a> is not <var>parent</var>,
then <a>throw</a> a "{{NotFoundError!!exception}}" {{DOMException}}.
</ol>

<p class=note>The <a>ensure pre-move validity</a> steps are a similar, but slightly stricter version
of the <a>ensure pre-insertion validity</a> steps.</p>

<p>To <dfn export id=concept-node-pre-move>pre-move</dfn> a <var>node</var> into a
<var>parent</var> before a <var>child</var>, run these steps:

<ol>
<li><p><a>Ensure pre-move validity</a> of <var>node</var> into <var>parent</var> before
<var>child</var>.

<li><p>Let <var>referenceChild</var> be <var>child</var>.

<li><p>If <var>referenceChild</var> is <var>node</var>, then set <var>referenceChild</var> to
<var>node</var>'s <a for=tree>next sibling</a>.

<li><p><a for=/>Move</a> <var>node</var> into <var>parent</var> before <var>referenceChild</var>.

<li><p>Return <var>node</var>.
</ol>

<p><a lt="Other applicable specifications">Specifications</a> may define
<dfn export id=concept-node-insert-ext>insertion steps</dfn> for all or some <a for=/>nodes</a>. The
algorithm is passed <var>insertedNode</var>, as indicated in the <a for=/>insert</a> algorithm
Expand Down Expand Up @@ -2845,6 +2889,125 @@ before a <var>child</var>, with an optional <i>suppress observers flag</i>, run
</ol>


<p><a lt="Other applicable specifications">Specifications</a> may define <dfn export
id=concept-moving-steps-ext>moving steps</dfn> for all or some <a for=/>nodes</a>. The algorithm is
passed a <a for=/>node</a> <var ignore>movedNode</var>, and a <a for=/>node</a>-or-null <var
ignore>oldParent</var> as indicated in the <a for=/>move</a> algorithm below. Like the <span
data-x="concept-insertion-steps-ext">insertion steps</span>, these steps must not modify the
<a>node tree</a> that <var>insertedNode</var> <a>participates</a> in, create
<a for=/>browsing contexts</a>, <a lt="fire an event">fire events</a>, or otherwise execute
JavaScript. These steps may queue tasks to do these things asynchronously, however.


<p>To <dfn export id=concept-node-move>move</dfn> a <var>node</var> into a <var>parent</var> before
a <var>child</var>, run these steps:

<ol>
<!-- Start removing-related bookkeeping steps -->
<li><p>Let <var>oldParent</var> be <var>node</var>'s <a for=tree>parent</a>.

<li><p><a>Assert</a>: <var>oldParent</var> is non-null.

<li><p>Let <var>index</var> be <var>node</var>'s <a for=tree>index</a>.

<li>
<p>For each <a>live range</a> whose <a for=range>start node</a> is <var>parent</var> and
<a for=range>start offset</a> is greater than <var>index</var>, decrease its
<a for=range>start offset</a> by 1.</p>

<p class="note">Note that unlike the traditional <a for=/ lt="remove">removal</a> case, we do not
need to update <a>live range</a> state when their <a for=range>start node</a> or
<a for=range>end node</a> is an <a>inclusive descendant</a> of the <var>node</var>. This is
because said <a>nodes</a> do not get removed from their <a>tree</a>, so ranges associated with
them stay intact.</p>
domfarolino marked this conversation as resolved.
Show resolved Hide resolved
</li>

<li><p>For each <a>live range</a> whose <a for=range>end node</a> is <var>parent</var> and
<a for=range>end offset</a> is greater than <var>index</var>, decrease its
<a for=range>end offset</a> by 1.

<li><p>For each {{NodeIterator}} object <var>iterator</var> whose
<a for=traversal>root</a>'s <a for=Node>node document</a> is <var>node</var>'s
<a for=Node>node document</a>, run the <a><code>NodeIterator</code> pre-removing steps</a> given
<var>node</var> and <var>iterator</var>.

<li><p>Let <var>oldPreviousSibling</var> be <var>node</var>'s <a>previous sibling</a>.

<li><p>Let <var>oldNextSibling</var> be <var>node</var>'s <a for=tree>next sibling</a>.

<li><p><a for=set>Remove</a> <var>node</var> from <var>oldParent</var>'s <a for=tree>children</a>.

<!-- Start insertion-related bookkeeping steps -->
<li>
<p>If <var>child</var> is non-null, then:

<ol>
<li><p>For each <a>live range</a> whose <a for=range>start node</a> is <var>parent</var> and
<a for=range>start offset</a> is greater than <var>child</var>'s <a for=tree>index</a>, increase
its <a for=range>start offset</a> by <var>count</var>.

<li><p>For each <a>live range</a> whose <a for=range>end node</a> is <var>parent</var> and
<a for=range>end offset</a> is greater than <var>child</var>'s <a for=tree>index</a>, increase
its <a for=range>end offset</a> by <var>count</var>.
</ol>

<li><p>Let <var>previousSibling</var> be <var>child</var>'s <a>previous sibling</a> or
<var>parent</var>'s <a>last child</a> if <var>child</var> is null.

<li><p>If <var>child</var> is null, then <a for=set>append</a> <var>node</var> to
<var>parent</var>'s <a for=tree>children</a>.

<li><p>Otherwise, <a for=set>insert</a> <var>node</var> into <var>parent</var>'s
<a for=tree>children</a> before <var>child</var>'s <a for=tree>index</a>.

<li><p>If <var>parent</var> is a <a for=Element>shadow host</a> whose <a for=/>shadow root</a>'s
<a for=ShadowRoot>slot assignment</a> is "<code>named</code>" and <var>node</var> is a
<a>slottable</a>, then <a>assign a slot</a> for <var>node</var>.

<li><p>If <var>parent</var>'s <a for=tree>root</a> is a <a for=/>shadow root</a>, and
<var>parent</var> is a <a>slot</a> whose <a for=slot>assigned nodes</a> is the empty list,
then run <a>signal a slot change</a> for <var>parent</var>.

<li><p>Run <a>assign slottables for a tree</a> with <var>node</var>'s <a for=tree>root</a>.

<li>
<p>For each <a>shadow-including inclusive descendant</a> <var>inclusiveDescendant</var> of
<var>node</var>, in <a>shadow-including tree order</a>:

<ol>
<li>
<p>If <var>inclusiveDescendant</var> is <var>node</var>, then run the <a>moving steps</a> with
<var>inclusiveDescendant</var> and <var>oldParent</var>. Otherwise, run the <a>moving steps</a>
with <var>inclusiveDescendant</var> and null.

<p class="note">Because the <a>move</a> algorithm is a separate primitive from
<a for=/>insert</a> and <a for=/>remove</a>, it does not invoke the traditional
<a>insertion steps</a> or <a>removing steps</a> for <var>inclusiveDescendant</var>.
</li>

<p>If <var>inclusiveDescendant</var> is <a for=Element>custom</a>, then
<a>enqueue a custom element callback reaction</a> with <var>inclusiveDescendant</var>, callback
name "<code>connectedMoveCallback</code>", and an empty argument list.

<li>
<p>Otherwise, <a lt="try to upgrade an element">try to upgrade</a>
<var>inclusiveDescendant</var>.

<p class=note>If this successfully upgrades <var>inclusiveDescendant</var>, its
<code>connectedCallback</code> will be enqueued automatically during the
<a>upgrade an element</a> algorithm.
</li>
domfarolino marked this conversation as resolved.
Show resolved Hide resolved
</ol>
</li>

<li><p><a>Queue a tree mutation record</a> for <var>parent</var> with « », <var>nodes</var>,
<var>oldPreviousSibling</var>, and <var>oldNextSibling</var>.</p></li>

<li><a>Queue a tree mutation record</a> for <var>parent</var> with <var>nodes</var>, « »,
<var>previousSibling</var>, and <var>child</var>.</p></li>
</ol>


<p>To <dfn export id=concept-node-append>append</dfn> a <var>node</var> to a <var>parent</var>,
<a>pre-insert</a> <var>node</var> into <var>parent</var> before null.

Expand Down Expand Up @@ -3977,8 +4140,8 @@ method steps are:
<li><p>Assert: either <var>addedNodes</var> or <var>removedNodes</var> <a for=set>is not empty</a>.

<li><p><a>Queue a mutation record</a> of "<code>childList</code>" for <var>target</var> with
null, null, null, <var>addedNodes</var>, <var>removedNodes</var>, <var>previousSibling</var>,
and <var>nextSibling</var>.
null, null, null, <var>addedNodes</var>, <var>removedNodes</var>, <var>previousSibling</var>, and
<var>nextSibling</var>.
domfarolino marked this conversation as resolved.
Show resolved Hide resolved
</ol>


Expand Down Expand Up @@ -4118,6 +4281,8 @@ interface Node : EventTarget {
[CEReactions] Node appendChild(Node node);
[CEReactions] Node replaceChild(Node node, Node child);
[CEReactions] Node removeChild(Node child);

domfarolino marked this conversation as resolved.
Show resolved Hide resolved
[CEReactions] Node moveBefore(Node node, Node? child);
};

dictionary GetRootNodeOptions {
Expand Down Expand Up @@ -4986,6 +5151,16 @@ within <a>this</a>.
<p>The <dfn method for=Node><code>removeChild(<var>child</var>)</code></dfn> method steps are to
return the result of <a>pre-removing</a> <var>child</var> from <a>this</a>.

<p>The <dfn method for=Node><code>moveBefore(<var>node</var>, <var>child</var>)</code></dfn>
method steps are:

<ol>
<li><p>Let <var>return node</var> be the result of <a>pre-moving</a> <var>node</var> into
<a>this</a> before <var>child</var>, rethrowing any exceptions.</p></li>
domfarolino marked this conversation as resolved.
Show resolved Hide resolved

<li><p>Return <var>return node</var>.</p></li>
</ol>

<hr><!-- Collections -->

<p>The
Expand Down Expand Up @@ -8096,8 +8271,8 @@ interface Range : AbstractRange {
<dfn export id=concept-live-range>live ranges</dfn>.

<p class=note>Algorithms that modify a <a>tree</a> (in particular the <a for=/>insert</a>,
<a for=/>remove</a>, <a>replace data</a>, and <a lt="split a Text node">split</a> algorithms) modify
<a>live ranges</a> associated with that <a>tree</a>.
<a for=/>remove</a>, <a for=/>move</a>, <a>replace data</a>, and <a lt="split a Text node">split</a>
algorithms) modify <a>live ranges</a> associated with that <a>tree</a>.

<p>The <dfn export id=concept-range-root for="live range">root</dfn> of a <a>live range</a> is the
<a for=tree>root</a> of its <a for=range>start node</a>.
Expand Down