Skip to content

Commit eaf2ac7

Browse files
domfarolinoannevk
andauthored
Add moveBefore(), a state-preserving atomic move API
This introduces a new API on the ParentNode mixin: moveBefore(). It mirrors insertBefore() in shape, but uses a new manipulation primitive that is also added here: "move". The move primitive contains some of the node tree bookkeeping steps from the remove primitive, as well as the insert primitive, and does three more interesting things: 1. Calls the moving steps hook with the moved node and the old parent (possibly null, just like the removing steps). 2. Queues a custom element callback reaction for the connectedMoveCallback(). 3. Queues two back-to-back mutation record tasks: one for removal from the old parent; one for insertion into the new one. The power of the move primitive comes from the fact that the algorithm does not defer to the traditional insert and remove primitives, and therefore does not invoke the removing steps and insertion steps. This allows most state to be preserved by default (i.e., we don't tear down iframes, or close dialogs). Sometimes, the insertion/removing step overrides in other specifications have steps that do need to be performed during a move anyways. These specifications are expected to override the moving steps hook and perform the necessary work accordingly. Corresponding HTML PR: whatwg/html#10657. Tests: https://github.com/web-platform-tests/wpt/tree/master/dom/nodes/moveBefore (the tentative directory will be flattened). Closes #1255. Closes #1335. Co-authored-by: Anne van Kesteren <[email protected]>
1 parent b64559c commit eaf2ac7

File tree

1 file changed

+192
-18
lines changed

1 file changed

+192
-18
lines changed

dom.bs

+192-18
Original file line numberDiff line numberDiff line change
@@ -2881,6 +2881,144 @@ before a <var>child</var>, with an optional <i>suppress observers flag</i>, run
28812881
</ol>
28822882

28832883

2884+
<p><a lt="Other applicable specifications">Specifications</a> may define
2885+
<dfn export id=concept-node-move-ext>moving steps</dfn> for all or some <a for=/>nodes</a>. The
2886+
algorithm is passed a <a for=/>node</a> <var ignore>movedNode</var>, and a <a for=/>node</a>-or-null
2887+
<var ignore>oldParent</var> as indicated in the <a for=/>move</a> algorithm below. Like the
2888+
<a>insertion steps</a>, these steps must not modify the <a>node tree</a> that
2889+
<var>movedNode</var> <a>participates</a> in, create <a for=/>browsing contexts</a>,
2890+
<a lt="fire an event">fire events</a>, or otherwise execute JavaScript. These steps may queue tasks
2891+
to do these things asynchronously, however.
2892+
2893+
2894+
<p>To <dfn>move</dfn> a <a for=/>node</a> <var>node</var> into a <a for=/>node</a>
2895+
<var>newParent</var> before a <a for=/>node</a>-or-null <var>child</var>:
2896+
2897+
<ol>
2898+
<!-- Start pre-move validity checks -->
2899+
<li>
2900+
<p>If <var>newParent</var>'s <a for=/>shadow-including root</a> is not the same as
2901+
<var>node</var>'s <a for=/>shadow-including root</a>, then <a>throw</a> a
2902+
"{{HierarchyRequestError!!exception}}" {{DOMException}}.</p>
2903+
2904+
<p class=note>This has the side effect of ensuring that a move is only performed if
2905+
<var>newParent</var>'s <a>connected</a> is <var>node</var>'s <a>connected</a>.</p>
2906+
</li>
2907+
2908+
<li><p>If <var>node</var> is a <a>host-including inclusive ancestor</a> of <var>newParent</var>,
2909+
then <a>throw</a> a "{{HierarchyRequestError!!exception}}" {{DOMException}}.
2910+
2911+
<li><p>If <var>child</var> is non-null and its <a for=tree>parent</a> is not <var>newParent</var>,
2912+
then <a>throw</a> a "{{NotFoundError!!exception}}" {{DOMException}}.
2913+
2914+
<li><p>If <var>node</var> is not an {{Element}} or a {{CharacterData}} <a for=/>node</a>, then
2915+
<a>throw</a> a "{{HierarchyRequestError!!exception}}" {{DOMException}}.</p></li>
2916+
2917+
<li><p>If <var>node</var> is a {{Text}} <a for=/>node</a> and <var>newParent</var> is a
2918+
<a for=/>document</a>, then <a>throw</a> a "{{HierarchyRequestError!!exception}}" {{DOMException}}.
2919+
2920+
<li><p>If <var>newParent</var> is a <a for=/>document</a>, <var>node</var> is an {{Element}}
2921+
<a for=/>node</a>, and either <var>newParent</var> has an <a for=/>element</a>
2922+
<a for=tree>child</a>, <var>child</var> is a <a>doctype</a>, or <var>child</var> is non-null and a
2923+
<a>doctype</a> is <a>following</a> <var>child</var> then <a>throw</a> a
2924+
"{{HierarchyRequestError!!exception}}" {{DOMException}}.
2925+
2926+
<!-- Start removing-related bookkeeping steps -->
2927+
<li><p>Let <var>oldParent</var> be <var>node</var>'s <a for=tree>parent</a>.
2928+
2929+
<li><p><a>Assert</a>: <var>oldParent</var> is non-null.
2930+
2931+
<li><p>Run the <a>live range pre-remove steps</a>, given <var>node</var>.
2932+
2933+
<li><p>For each {{NodeIterator}} object <var>iterator</var> whose
2934+
<a for=traversal>root</a>'s <a for=Node>node document</a> is <var>node</var>'s
2935+
<a for=Node>node document</a>, run the <a><code>NodeIterator</code> pre-remove steps</a> given
2936+
<var>node</var> and <var>iterator</var>.
2937+
2938+
<li><p>Let <var>oldPreviousSibling</var> be <var>node</var>'s <a>previous sibling</a>.
2939+
2940+
<li><p>Let <var>oldNextSibling</var> be <var>node</var>'s <a for=tree>next sibling</a>.
2941+
2942+
<li><p><a for=set>Remove</a> <var>node</var> from <var>oldParent</var>'s <a for=tree>children</a>.
2943+
2944+
<li><p>If <var>node</var> is <a for=slottable>assigned</a>, then run <a>assign slottables</a> for
2945+
<var>node</var>'s <a>assigned slot</a>.
2946+
2947+
<li><p>If <var>oldParent</var>'s <a for=tree>root</a> is a <a for=/>shadow root</a>, and
2948+
<var>oldParent</var> is a <a>slot</a> whose <a for=slot>assigned nodes</a>
2949+
<a for=list>is empty</a>, then run <a>signal a slot change</a> for <var>oldParent</var>.
2950+
2951+
<li>
2952+
<p>If <var>node</var> has an <a>inclusive descendant</a> that is a <a>slot</a>:
2953+
2954+
<ol>
2955+
<li><p>Run <a>assign slottables for a tree</a> with <var>oldParent</var>'s <a for=tree>root</a>.
2956+
2957+
<li><p>Run <a>assign slottables for a tree</a> with <var>node</var>.
2958+
</ol>
2959+
2960+
<!-- Start insertion-related bookkeeping steps -->
2961+
<li>
2962+
<p>If <var>child</var> is non-null:
2963+
2964+
<ol>
2965+
<li><p>For each <a>live range</a> whose <a for=range>start node</a> is <var>newParent</var> and
2966+
<a for=range>start offset</a> is greater than <var>child</var>'s <a for=tree>index</a>, increase
2967+
its <a for=range>start offset</a> by 1.
2968+
2969+
<li><p>For each <a>live range</a> whose <a for=range>end node</a> is <var>newParent</var> and
2970+
<a for=range>end offset</a> is greater than <var>child</var>'s <a for=tree>index</a>, increase
2971+
its <a for=range>end offset</a> by 1.
2972+
</ol>
2973+
2974+
<li><p>Let <var>newPreviousSibling</var> be <var>child</var>'s <a>previous sibling</a> if
2975+
<var>child</var> is non-null, and <var>newParent</var>'s <a>last child</a> otherwise.
2976+
2977+
<li><p>If <var>child</var> is null, then <a for=set>append</a> <var>node</var> to
2978+
<var>newParent</var>'s <a for=tree>children</a>.
2979+
2980+
<li><p>Otherwise, <a for=set>insert</a> <var>node</var> into <var>newParent</var>'s
2981+
<a for=tree>children</a> before <var>child</var>'s <a for=tree>index</a>.
2982+
2983+
<li><p>If <var>newParent</var> is a <a for=Element>shadow host</a> whose <a for=/>shadow root</a>'s
2984+
<a for=ShadowRoot>slot assignment</a> is "<code>named</code>" and <var>node</var> is a
2985+
<a>slottable</a>, then <a>assign a slot</a> for <var>node</var>.
2986+
2987+
<li><p>If <var>newParent</var>'s <a for=tree>root</a> is a <a for=/>shadow root</a>, and
2988+
<var>newParent</var> is a <a>slot</a> whose <a for=slot>assigned nodes</a>
2989+
<a for=list>is empty</a>, then run <a>signal a slot change</a> for <var>newParent</var>.
2990+
2991+
<li><p>Run <a>assign slottables for a tree</a> with <var>node</var>'s <a for=tree>root</a>.
2992+
2993+
<li>
2994+
<p>For each <a>shadow-including inclusive descendant</a> <var>inclusiveDescendant</var> of
2995+
<var>node</var>, in <a>shadow-including tree order</a>:
2996+
2997+
<ol>
2998+
<li>
2999+
<p>If <var>inclusiveDescendant</var> is <var>node</var>, then run the <a>moving steps</a> with
3000+
<var>inclusiveDescendant</var> and <var>oldParent</var>. Otherwise, run the <a>moving steps</a>
3001+
with <var>inclusiveDescendant</var> and null.
3002+
3003+
<p class="note">Because the <a>move</a> algorithm is a separate primitive from
3004+
<a for=/>insert</a> and <a for=/>remove</a>, it does not invoke the traditional
3005+
<a>insertion steps</a> or <a>removing steps</a> for <var>inclusiveDescendant</var>.
3006+
</li>
3007+
3008+
<li><p>If <var>inclusiveDescendant</var> is <a for=Element>custom</a> and <var>newParent</var> is
3009+
<a>connected</a>, then <a>enqueue a custom element callback reaction</a> with
3010+
<var>inclusiveDescendant</var>, callback name "<code>connectedMoveCallback</code>", and « ».
3011+
</ol>
3012+
</li>
3013+
3014+
<li><p><a>Queue a tree mutation record</a> for <var>oldParent</var> with « », « <var>node</var> »,
3015+
<var>oldPreviousSibling</var>, and <var>oldNextSibling</var>.</p></li>
3016+
3017+
<li><p><a>Queue a tree mutation record</a> for <var>newParent</var> with « <var>node</var> », « »,
3018+
<var>newPreviousSibling</var>, and <var>child</var>.</p></li>
3019+
</ol>
3020+
3021+
28843022
<p>To <dfn export id=concept-node-append>append</dfn> a <var>node</var> to a <var>parent</var>,
28853023
<a>pre-insert</a> <var>node</var> into <var>parent</var> before null.
28863024

@@ -3027,22 +3165,7 @@ optional <i>suppress observers flag</i>, run these steps:
30273165

30283166
<li><p>Assert: <var>parent</var> is non-null.
30293167

3030-
<li><p>Let <var>index</var> be <var>node</var>'s <a for=tree>index</a>.
3031-
3032-
<li><p>For each <a>live range</a> whose <a for=range>start node</a> is an
3033-
<a>inclusive descendant</a> of <var>node</var>, set its <a for=range>start</a> to
3034-
(<var>parent</var>, <var>index</var>).
3035-
3036-
<li><p>For each <a>live range</a> whose <a for=range>end node</a> is an <a>inclusive descendant</a>
3037-
of <var>node</var>, set its <a for=range>end</a> to (<var>parent</var>, <var>index</var>).
3038-
3039-
<li><p>For each <a>live range</a> whose <a for=range>start node</a> is <var>parent</var> and
3040-
<a for=range>start offset</a> is greater than <var>index</var>, decrease its
3041-
<a for=range>start offset</a> by 1.
3042-
3043-
<li><p>For each <a>live range</a> whose <a for=range>end node</a> is <var>parent</var> and
3044-
<a for=range>end offset</a> is greater than <var>index</var>, decrease its
3045-
<a for=range>end offset</a> by 1.
3168+
<li><p>Run the <a>live range pre-remove steps</a>, given <var>node</var>.
30463169

30473170
<li><p>For each {{NodeIterator}} object <var>iterator</var> whose
30483171
<a for=traversal>root</a>'s <a for=Node>node document</a> is <var>node</var>'s
@@ -3187,6 +3310,8 @@ interface mixin ParentNode {
31873310
[CEReactions, Unscopable] undefined append((Node or DOMString)... nodes);
31883311
[CEReactions, Unscopable] undefined replaceChildren((Node or DOMString)... nodes);
31893312

3313+
[CEReactions] undefined moveBefore(Node node, Node? child);
3314+
31903315
Element? querySelector(DOMString selectors);
31913316
[NewObject] NodeList querySelectorAll(DOMString selectors);
31923317
};
@@ -3234,6 +3359,17 @@ Element includes ParentNode;
32343359
the <a>node tree</a> are violated.
32353360
<!-- "NotFoundError" is impossible -->
32363361

3362+
<dt><code><var>node</var> . <a method for=ParentNode lt="moveBefore()">moveBefore</a>(<var>movedNode</var>, <var>child</var>)</code>
3363+
<dd>
3364+
<p>Moves, without first removing, <var>movedNode</var> into <var>node</var> after <var>child</var>
3365+
if <var>child</var> is non-null; otherwise after the <a>last child</a> of <var>node</var>. This
3366+
method preserves state associated with <var>movedNode</var>.
3367+
3368+
<p><a>Throws</a> a "{{HierarchyRequestError!!exception}}" {{DOMException}} if the constraints of
3369+
the <a>node tree</a> are violated, or the state associated with the moved node cannot be
3370+
preserved.
3371+
<!-- "NotFoundError" is impossible -->
3372+
32373373
<dt><code><var>node</var> . <a method for=ParentNode lt="querySelector()">querySelector</a>(<var>selectors</var>)</code>
32383374
<dd><p>Returns the first <a for=/>element</a> that is a <a for=tree>descendant</a> of
32393375
<var>node</var> that matches <var>selectors</var>.
@@ -3287,6 +3423,18 @@ are:
32873423
<li><p><a for=Node>Replace all</a> with <var>node</var> within <a>this</a>.
32883424
</ol>
32893425

3426+
<p>The <dfn method for=ParentNode><code>moveBefore(<var>node</var>, <var>child</var>)</code></dfn>
3427+
method steps are:
3428+
3429+
<ol>
3430+
<li><p>Let <var>referenceChild</var> be <var>child</var>.
3431+
3432+
<li><p>If <var>referenceChild</var> is <var>node</var>, then set <var>referenceChild</var> to
3433+
<var>node</var>'s <a for=tree>next sibling</a>.
3434+
3435+
<li><p><a for=/>Move</a> <var>node</var> into <a>this</a> before <var>referenceChild</var>.
3436+
</ol>
3437+
32903438
<p>The <dfn method for=ParentNode><code>querySelector(<var>selectors</var>)</code></dfn> method
32913439
steps are to return the first result of running <a>scope-match a selectors string</a>
32923440
<var>selectors</var> against <a>this</a>, if the result is not an empty list; otherwise null.
@@ -8154,8 +8302,8 @@ interface Range : AbstractRange {
81548302
<dfn export id=concept-live-range>live ranges</dfn>.
81558303

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

81608308
<p>The <dfn export id=concept-range-root for="live range">root</dfn> of a <a>live range</a> is the
81618309
<a for=tree>root</a> of its <a for=range>start node</a>.
@@ -8221,6 +8369,32 @@ but not its <a for=range>end node</a>, or vice versa.
82218369
</ul>
82228370
</div>
82238371

8372+
<p>The <dfn>live range pre-remove steps</dfn> given a <a for=/>node</a> <var>node</var>, are as
8373+
follows:
8374+
8375+
<ol>
8376+
<li><p>Let <var>parent</var> be <var>node</var>'s <a for=tree>parent</a>.
8377+
8378+
<li><p><a>Assert</a>: <var>parent</var> is not null.
8379+
8380+
<li><p>Let <var>index</var> be <var>node</var>'s <a for=tree>index</a>.
8381+
8382+
<li><p>For each <a>live range</a> whose <a for=range>start node</a> is an
8383+
<a>inclusive descendant</a> of <var>node</var>, set its <a for=range>start</a> to
8384+
(<var>parent</var>, <var>index</var>).
8385+
8386+
<li><p>For each <a>live range</a> whose <a for=range>end node</a> is an <a>inclusive descendant</a>
8387+
of <var>node</var>, set its <a for=range>end</a> to (<var>parent</var>, <var>index</var>).
8388+
8389+
<li><p>For each <a>live range</a> whose <a for=range>start node</a> is <var>parent</var> and
8390+
<a for=range>start offset</a> is greater than <var>index</var>, decrease its
8391+
<a for=range>start offset</a> by 1.
8392+
8393+
<li><p>For each <a>live range</a> whose <a for=range>end node</a> is <var>parent</var> and
8394+
<a for=range>end offset</a> is greater than <var>index</var>, decrease its
8395+
<a for=range>end offset</a> by 1.
8396+
</ol>
8397+
82248398
<hr>
82258399

82268400
<dl class=domintro>

0 commit comments

Comments
 (0)