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 55 commits into
base: main
Choose a base branch
from
Open
Changes from 54 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
0d30952
Initial atomic move skeleton
domfarolino Aug 19, 2024
3073a9c
Most of the `MutationObserver` integration
domfarolino Aug 26, 2024
6670500
Remove mutation events flag references
domfarolino Aug 26, 2024
85d32be
Populate the `MutationRecord`
domfarolino Aug 26, 2024
945189c
Fix punctuation
domfarolino Aug 26, 2024
8ecd282
Remove trailing whitespace
domfarolino Aug 26, 2024
b798d9a
`<span>` -> `<a>` plus fix punctuation error
domfarolino Aug 27, 2024
e5bb880
Camel case
domfarolino Aug 27, 2024
70e1531
Throw an exception on failure
domfarolino Sep 24, 2024
512ab18
Introduce move primitive + moving steps hook/extension
domfarolino Sep 30, 2024
2afc546
Remove changes to insertion primitive
domfarolino Oct 10, 2024
0862078
Revert document flag and mutation record changes
domfarolino Oct 10, 2024
c59a7e5
Fix mutation record callsites
domfarolino Oct 10, 2024
30fc53c
Fix wrapping
domfarolino Oct 10, 2024
c53fba8
Remove suppress observers flag
domfarolino Oct 10, 2024
47e97f0
Custom element integration
domfarolino Oct 10, 2024
75f5c75
Update live ranges and NodeIterators properly; do not call the remove…
domfarolino Oct 10, 2024
5aa8a70
Correct removal bookkeeping
domfarolino Oct 10, 2024
fe7c678
Moving steps prose
domfarolino Oct 15, 2024
cfe40ff
Tighten up pre-move checks
domfarolino Oct 15, 2024
1d0f90e
Assert -> throw condition
domfarolino Oct 15, 2024
abb3c4a
Move conditions into pre-move validity
domfarolino Oct 15, 2024
73bdd6d
Fix `<old>`
domfarolino Oct 15, 2024
3ae1115
Ordering and format
domfarolino Oct 16, 2024
45d089c
Fix live range updating logic
domfarolino Nov 7, 2024
62cf625
Document `move` primitive in Range note
domfarolino Nov 7, 2024
457c410
Enable moves in a connected `ShadowRoot` `DocumentFragment` node
domfarolino Nov 11, 2024
41b1742
Do not explicitly rethrow
domfarolino Nov 13, 2024
fedd42b
Do not do special range handling
domfarolino Nov 19, 2024
cb549ca
Support both connected->connected and disconnected->disconnected
domfarolino Nov 20, 2024
2bc9213
Whitespace and formatting
domfarolino Nov 20, 2024
f1cf126
Factor our live range pre-removal steps
domfarolino Nov 27, 2024
a40701d
newParent, newPreviousSibling, and count
domfarolino Nov 27, 2024
7bcdeb2
Only queue connectedMoveCallback if connected
domfarolino Nov 27, 2024
22ff396
Revert random editorial change
domfarolino Nov 27, 2024
9478d7f
Remove newline
domfarolino Nov 27, 2024
fd643dc
Remove manual custom element upgrade
domfarolino Nov 27, 2024
3d0651d
Compare node documents instead of shadow-including roots
domfarolino Dec 9, 2024
f5c1e83
Align pre-move and pre-insertion conditions
domfarolino Dec 10, 2024
bc64f9a
Add document-is-parent pre-move conditions
domfarolino Dec 10, 2024
d6436f4
Remove unnecessary checks now that we have root checks
domfarolino Dec 12, 2024
41e8864
Live range rename
domfarolino Dec 12, 2024
7c781c5
Empty arguments
domfarolino Dec 12, 2024
b0a10e4
Update dom.bs
domfarolino Dec 12, 2024
21ee3c1
Update dom.bs
domfarolino Dec 12, 2024
3c8ba6d
Remove run these steps
domfarolino Dec 12, 2024
c6aaace
Move to ParentNode, simplify pre-move conditions, add web developer box
domfarolino Dec 12, 2024
227fd9d
nits
annevk Dec 13, 2024
145d726
nit
annevk Dec 13, 2024
4b54a0d
actually, pre-removing here is better
annevk Dec 13, 2024
90d8339
Make moveBefore() return undefined
domfarolino Dec 16, 2024
8e1d36d
Fold more into the move algorithm
domfarolino Dec 17, 2024
bb1a060
Document for reference, and this->newParent
domfarolino Dec 17, 2024
f3f2ab2
Update moving steps link to be consistent with other extension hooks,…
domfarolino Dec 17, 2024
32a8a68
Fire slotchange events on the removal and insertion during moveBefore
domfarolino Dec 20, 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
193 changes: 175 additions & 18 deletions dom.bs
Original file line number Diff line number Diff line change
Expand Up @@ -2872,6 +2872,128 @@ 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-node-move-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>move</dfn> a <a for=/>node</a> <var>node</var> into a <a for=/>node</a>
<var>newParent</var> before a <a for=/>node</a> <var>child</var>:

<ol>
<!-- Start pre-move validity checks -->
<li>
<p>If <var>newParent</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>

<p class=note>This has the side effect of ensuring that a move is only performed if
<var>newParent</var>'s <a>connected</a> is <var>node</var>'s <a>connected</a>.</p>
</li>

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

<li><p>If <var>child</var> is non-null and its <a for=tree>parent</a> is not <var>newParent</var>,
then <a>throw</a> a "{{NotFoundError!!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>node</var> is a {{Text}} <a for=/>node</a> and <var>newParent</var> is a
<a for=/>document</a>, then <a>throw</a> a "{{HierarchyRequestError!!exception}}" {{DOMException}}.

<li><p>If <var>newParent</var> is a <a for=/>document</a>, <var>node</var> is an {{Element}}
<a for=/>node</a>, and either <var>newParent</var> has an <a for=/>element</a>
<a for=tree>child</a>, <var>child</var> is a <a>doctype</a>, or <var>child</var> is non-null and a
<a>doctype</a> is <a>following</a> <var>child</var> then <a>throw</a> a
"{{HierarchyRequestError!!exception}}" {{DOMException}}.

<!-- 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>Run the <a>live range pre-remove steps</a>, given <var>node</var>.

<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-remove 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:

<ol>
<li><p>For each <a>live range</a> whose <a for=range>start node</a> is <var>newParent</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 1.

<li><p>For each <a>live range</a> whose <a for=range>end node</a> is <var>newParent</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 1.
</ol>

<li><p>Let <var>newPreviousSibling</var> be <var>child</var>'s <a>previous sibling</a> if
<var>child</var> is non-null, and <var>newParent</var>'s <a>last child</a> otherwise.

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

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

<li><p>If <var>newParent</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>newParent</var>'s <a for=tree>root</a> is a <a for=/>shadow root</a>, and
<var>newParent</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>newParent</var>.

<li><p>Run <a>assign slottables for a tree</a> with <var>node</var>'s <a for=tree>root</a>.
domfarolino marked this conversation as resolved.
Show resolved Hide resolved

<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>

<li><p>If <var>inclusiveDescendant</var> is <a for=Element>custom</a> and <var>newParent</var> is
<a>connected</a>, then <a>enqueue a custom element callback reaction</a> with
<var>inclusiveDescendant</var>, callback name "<code>connectedMoveCallback</code>", and « ».
</ol>
</li>

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

<li><a>Queue a tree mutation record</a> for <var>newParent</var> with « <var>node</var> », « »,
<var>newPreviousSibling</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 @@ -3018,22 +3140,7 @@ optional <i>suppress observers flag</i>, run these steps:

<li><p>Assert: <var>parent</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 an
<a>inclusive descendant</a> of <var>node</var>, set its <a for=range>start</a> to
(<var>parent</var>, <var>index</var>).

<li><p>For each <a>live range</a> whose <a for=range>end node</a> is an <a>inclusive descendant</a>
of <var>node</var>, set its <a for=range>end</a> to (<var>parent</var>, <var>index</var>).

<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.

<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>Run the <a>live range pre-remove steps</a>, given <var>node</var>.

<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
Expand Down Expand Up @@ -3178,6 +3285,8 @@ interface mixin ParentNode {
[CEReactions, Unscopable] undefined append((Node or DOMString)... nodes);
[CEReactions, Unscopable] undefined replaceChildren((Node or DOMString)... nodes);

[CEReactions] undefined moveBefore(Node node, Node? child);

Element? querySelector(DOMString selectors);
[NewObject] NodeList querySelectorAll(DOMString selectors);
};
Expand Down Expand Up @@ -3225,6 +3334,16 @@ Element includes ParentNode;
the <a>node tree</a> are violated.
<!-- "NotFoundError" is impossible -->

<dt><code><var>node</var> . <a method for=ParentNode lt="moveBefore()">moveBefore</a>(<var>movedNode</var>, <var>child</var>)</code>
<dd>
<p>Moves, without first removing, <var>movedNode</var> into <var>node</var> after <var>child</var>
if <var>child</var> is non-null; otherwise after the <a>last child</a> of <var>node</var>. This
methods preserves state associated with <var>movedNode</var> so that it persists after the move.

<p><a>Throws</a> a "{{HierarchyRequestError!!exception}}" {{DOMException}} if the constraints of
the <a>node tree</a> are violated.
<!-- "NotFoundError" is impossible -->

<dt><code><var>node</var> . <a method for=ParentNode lt="querySelector()">querySelector</a>(<var>selectors</var>)</code>
<dd><p>Returns the first <a for=/>element</a> that is a <a for=tree>descendant</a> of
<var>node</var> that matches <var>selectors</var>.
Expand Down Expand Up @@ -3278,6 +3397,18 @@ are:
<li><p><a for=Node>Replace all</a> with <var>node</var> within <a>this</a>.
</ol>

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

<ol>
<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 <a>this</a> before <var>referenceChild</var>.
</ol>

<p>The <dfn method for=ParentNode><code>querySelector(<var>selectors</var>)</code></dfn> method
steps are to return the first result of running <a>scope-match a selectors string</a>
<var>selectors</var> against <a>this</a>, if the result is not an empty list; otherwise null.
Expand Down Expand Up @@ -8112,8 +8243,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 Expand Up @@ -8179,6 +8310,32 @@ but not its <a for=range>end node</a>, or vice versa.
</ul>
</div>

<p>The <dfn>live range pre-remove steps</dfn> given a <a for=/>node</a> <var>node</var>, are as
follows:

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

<li><p><a>Assert</a>: <var>parent</var> is not 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 an
<a>inclusive descendant</a> of <var>node</var>, set its <a for=range>start</a> to
(<var>parent</var>, <var>index</var>).

<li><p>For each <a>live range</a> whose <a for=range>end node</a> is an <a>inclusive descendant</a>
of <var>node</var>, set its <a for=range>end</a> to (<var>parent</var>, <var>index</var>).

<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.

<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.
</ol>

<hr>

<dl class=domintro>
Expand Down