-
Notifications
You must be signed in to change notification settings - Fork 300
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
domfarolino
wants to merge
57
commits into
main
Choose a base branch
from
state-preserving-atomic-move
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+182
−18
Open
Changes from 55 commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
0d30952
Initial atomic move skeleton
domfarolino 3073a9c
Most of the `MutationObserver` integration
domfarolino 6670500
Remove mutation events flag references
domfarolino 85d32be
Populate the `MutationRecord`
domfarolino 945189c
Fix punctuation
domfarolino 8ecd282
Remove trailing whitespace
domfarolino b798d9a
`<span>` -> `<a>` plus fix punctuation error
domfarolino e5bb880
Camel case
domfarolino 70e1531
Throw an exception on failure
domfarolino 512ab18
Introduce move primitive + moving steps hook/extension
domfarolino 2afc546
Remove changes to insertion primitive
domfarolino 0862078
Revert document flag and mutation record changes
domfarolino c59a7e5
Fix mutation record callsites
domfarolino 30fc53c
Fix wrapping
domfarolino c53fba8
Remove suppress observers flag
domfarolino 47e97f0
Custom element integration
domfarolino 75f5c75
Update live ranges and NodeIterators properly; do not call the remove…
domfarolino 5aa8a70
Correct removal bookkeeping
domfarolino fe7c678
Moving steps prose
domfarolino cfe40ff
Tighten up pre-move checks
domfarolino 1d0f90e
Assert -> throw condition
domfarolino abb3c4a
Move conditions into pre-move validity
domfarolino 73bdd6d
Fix `<old>`
domfarolino 3ae1115
Ordering and format
domfarolino 45d089c
Fix live range updating logic
domfarolino 62cf625
Document `move` primitive in Range note
domfarolino 457c410
Enable moves in a connected `ShadowRoot` `DocumentFragment` node
domfarolino 41b1742
Do not explicitly rethrow
domfarolino fedd42b
Do not do special range handling
domfarolino cb549ca
Support both connected->connected and disconnected->disconnected
domfarolino 2bc9213
Whitespace and formatting
domfarolino f1cf126
Factor our live range pre-removal steps
domfarolino a40701d
newParent, newPreviousSibling, and count
domfarolino 7bcdeb2
Only queue connectedMoveCallback if connected
domfarolino 22ff396
Revert random editorial change
domfarolino 9478d7f
Remove newline
domfarolino fd643dc
Remove manual custom element upgrade
domfarolino 3d0651d
Compare node documents instead of shadow-including roots
domfarolino f5c1e83
Align pre-move and pre-insertion conditions
domfarolino bc64f9a
Add document-is-parent pre-move conditions
domfarolino d6436f4
Remove unnecessary checks now that we have root checks
domfarolino 41e8864
Live range rename
domfarolino 7c781c5
Empty arguments
domfarolino b0a10e4
Update dom.bs
domfarolino 21ee3c1
Update dom.bs
domfarolino 3c8ba6d
Remove run these steps
domfarolino c6aaace
Move to ParentNode, simplify pre-move conditions, add web developer box
domfarolino 227fd9d
nits
annevk 145d726
nit
annevk 4b54a0d
actually, pre-removing here is better
annevk 90d8339
Make moveBefore() return undefined
domfarolino 8e1d36d
Fold more into the move algorithm
domfarolino bb1a060
Document for reference, and this->newParent
domfarolino f3f2ab2
Update moving steps link to be consistent with other extension hooks,…
domfarolino 32a8a68
Fire slotchange events on the removal and insertion during moveBefore
domfarolino de2d428
Remove incorrect `data-x` and `span`
domfarolino 5ce7c8a
Wrapping
domfarolino File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2872,6 +2872,135 @@ 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 | ||
domfarolino marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<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>. | ||
|
||
<li><p>If <var>node</var> is <a for=slottable>assigned</a>, then run <a>assign slottables</a> for | ||
<var>node</var>'s <a>assigned slot</a>. | ||
|
||
<li><p>If <var>oldParent</var>'s <a for=tree>root</a> is a <a for=/>shadow root</a>, and | ||
<var>oldParent</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>oldParent</var>. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aren't we missing the steps needed to track the case when slot element itself if moving. In the "remove" algorithm step 14. |
||
|
||
<!-- 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. | ||
|
||
|
@@ -3018,22 +3147,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 | ||
|
@@ -3178,6 +3292,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); | ||
}; | ||
|
@@ -3225,6 +3341,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>. | ||
|
@@ -3278,6 +3404,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. | ||
|
@@ -8112,8 +8250,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>. | ||
|
@@ -8179,6 +8317,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> | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DOM standard doesn't wrap on all spaces. Phrasing-level elements need to be kept on a single line. (This applies several times in this PR.)
It also seems okay to drop the explicit ID here as we stopped doing those.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed this instance, after finishing rewrapping this paragraph, no more jumped out at me. Maybe you have others in mind further down.
Yeah I realize it isn't necessary, however I liked the formatting of
concept-node-move-ext
since it perfectly matches the other primitives that HTML links to, so it seemed nice for link consistency: /~https://github.com/whatwg/html/pull/10657/files#diff-41cf6794ba4200b839c53531555f0f3998df4cbb01a4d5cb0b94e3ca5e23947dR3290-R3291. If you want to remove it, feel free, just remember to update the HTML PR. Otherwise, I'd slightly slightly prefer keeping as-is.