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

State-preserving atomic move integration #10657

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
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
142 changes: 119 additions & 23 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -1773,8 +1773,9 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
data-x="control">controls</span> or U+0020 SPACE).</p>

<p>An HTML element can have specific <dfn>HTML element insertion steps</dfn>, <dfn>HTML element
post-connection steps</dfn>, and <dfn>HTML element removing steps</dfn>, all defined for the
element's <span data-x="concept-element-local-name">local name</span>.</p>
post-connection steps</dfn>, <dfn>HTML element removing steps</dfn>, and <dfn>HTML element moving
steps</dfn> all defined for the element's <span data-x="concept-element-local-name">local
name</span>.</p>

<p>The <span data-x="concept-node-insert-ext">insertion steps</span> for the HTML Standard, given
<var>insertedNode</var>, are defined as the following:</p>
Expand Down Expand Up @@ -1861,6 +1862,29 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
popover algorithm</span> given <var>removedNode</var>, false, false, and false.</p></li>
</ol>

<p>The <span data-x="concept-node-move-ext">moving steps</span> for the HTML Standard, given
<var>movedNode</var>, are defined as the following:</p>

<ol>
<li><p>If <var>movedNode</var> is an element whose <span
data-x="concept-element-namespace">namespace</span> is the <span>HTML namespace</span>, and this
standard defines <span>HTML element moving steps</span> for <var>movedNode</var>'s <span
data-x="concept-element-local-name">local name</span>, then run the corresponding <span>HTML
element moving steps</span> given <var>movedNode</var>.</p></li>

<li>
<p>If <var>movedNode</var> is a <span>form-associated element</span> or the ancestor of a
<span>form-associated element</span>, then:</p>

<ol>
<li><p>If the <span>form-associated element</span>'s <span>parser inserted flag</span> is set,
then return.</p></li>

Choose a reason for hiding this comment

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

I assume this special case is here because something similar happens currently when removing and adding such element back?

Copy link
Member Author

@domfarolino domfarolino Nov 20, 2024

Choose a reason for hiding this comment

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

See the first row in https://docs.google.com/document/d/1qfYyvdK4zhzloABKeh0K1lHPm-SpnEcsWEE9UdDuoMk/edit?tab=t.0 for this in general.

However regarding this condition specifically, if I remember correctly it was just copied from the insertion steps to preserve the behavior of remove+insert during a move. Looking at it again, I'm unsure how this is supposed to work though.

Imagine a form-associated element with a form owner being removed from the DOM. There are two cases:

  1. During removal, that element gets its form owner reset because it hits the conditions in the "HTML element removing steps". This unsets the parser inserted bit. Next time that element gets inserted into the DOM, we'll reset the form owner again, always to the nearest form element.
  2. During removal, that element does not get its form owner reset. The parser-inserted bit remains set even though the element is disconnected. When it's inserted next, we trip the insertion condition "If the parser inserted flag is set", and we do not reset the form owner, ever.

It isn't clear to me when (2) above can ever happen. That is, when during removal will we not "reset the form owner" of a form-associated element that has a non-null form owner? The condition reads:

If the form-associated element has a form owner and the form-associated element and its form owner are no longer in the same tree, then reset the form owner of the form-associated element.

So it seems that (during removal) we'd avoid resetting the owner if the element is still in the same tree as its form owner. But removing makes an element disconnected, so I don't think it could ever be in the same tree as anything. I'm left to think that:

  1. There's a bug in the current HTML element removing steps
  2. Removed form-associated elements with non-null form owners are always reset (during removal)
  3. We don't need the parser inserted condition in the HTML element moving steps

If I am wrong about (1)/(2) in the immediately-above list, then we may still need the parser inserted condition in the moving steps.

I don't know much about forms and their associated mechanics but I feel like @zcorpan does and would maybe be willing to take a look here.


<li><p><span>Reset the form owner</span> of the <span>form-associated element</span>.</p></li>
</ol>
</li>
</ol>

<p>A <dfn id="insert-an-element-into-a-document" data-x="node is inserted into a document"
data-lt="inserted into a document|node is inserted into a document" export>node is inserted into a
document</dfn> when the <span data-x="concept-node-insert-ext">insertion steps</span> are invoked
Expand Down Expand Up @@ -3218,6 +3242,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<li><dfn data-x="dom-Node-append" data-x-href="https://dom.spec.whatwg.org/#dom-node-append"><code>append()</code></dfn> method</li>
<li><dfn data-x="dom-Node-appendChild" data-x-href="https://dom.spec.whatwg.org/#dom-node-appendchild"><code>appendChild()</code></dfn> method</li>
<li><dfn data-x="dom-Node-cloneNode" data-x-href="https://dom.spec.whatwg.org/#dom-node-clonenode"><code>cloneNode()</code></dfn> method</li>
<li><dfn data-x="dom-Node-moveBefore" data-x-href="https://dom.spec.whatwg.org/#dom-node-moveBefore"><code>moveBefore()</code></dfn> method</li>
<li><dfn data-x="dom-Document-importNode" data-x-href="https://dom.spec.whatwg.org/#dom-document-importnode"><code>importNode()</code></dfn> method</li>
<li><dfn data-x="dom-Event-preventDefault" data-x-href="https://dom.spec.whatwg.org/#dom-event-preventdefault"><code>preventDefault()</code></dfn> method</li>
<li><dfn data-x="dom-Element-id" data-x-href="https://dom.spec.whatwg.org/#dom-element-id"><code>id</code></dfn> attribute</li>
Expand Down Expand Up @@ -3257,6 +3282,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<li>The <dfn data-x="concept-node-insert-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-insert-ext">insertion steps</dfn>,
<li>The <dfn data-x="concept-node-post-insert-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-post-connection-ext">post-connection steps</dfn>,
<dfn data-x="concept-node-remove-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-remove-ext">removing steps</dfn>,
<dfn data-x="concept-node-move-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-move-ext">moving steps</dfn>,
<dfn data-x="concept-node-adopt-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-adopt-ext">adopting steps</dfn>, and
<dfn data-x-href="https://dom.spec.whatwg.org/#concept-node-children-changed-ext">children changed steps</dfn> hooks for elements</li>
<li>The <dfn data-x="concept-element-attributes-change" data-x-href="https://dom.spec.whatwg.org/#concept-element-attributes-change">change</dfn>, <dfn data-x="concept-element-attributes-append" data-x-href="https://dom.spec.whatwg.org/#concept-element-attributes-append">append</dfn>, <dfn data-x="concept-element-attributes-remove" data-x-href="https://dom.spec.whatwg.org/#concept-element-attributes-remove">remove</dfn>, <dfn data-x="concept-element-attributes-replace" data-x-href="https://dom.spec.whatwg.org/#concept-element-attributes-replace">replace</dfn>, <dfn data-x="concept-element-attributes-get-by-namespace" data-x-href="https://dom.spec.whatwg.org/#concept-element-attributes-get-by-namespace">get an attribute by namespace and local name</dfn>, <dfn data-x="concept-element-attributes-set-value" data-x-href="https://dom.spec.whatwg.org/#concept-element-attributes-set-value">set value</dfn>, and <dfn data-x="concept-element-attributes-remove-by-namespace" data-x-href="https://dom.spec.whatwg.org/#concept-element-attributes-remove-by-namespace">remove an attribute by namespace and local name</dfn> algorithms for attributes</li>
Expand Down Expand Up @@ -29020,8 +29046,8 @@ interface <dfn interface>HTMLSourceElement</dfn> : <span>HTMLElement</span> {

<div w-nodev>

<p>The <code>source</code> <span data-x="html element insertion steps">HTML element insertion
steps</span>, given <var>insertedNode</var>, are:</p>
<p>The <code>source</code> <span>HTML element insertion steps</span>, given
<var>insertedNode</var>, are:</p>

<ol>
<li><p>If <var>insertedNode</var>'s parent is a <span>media element</span> that has no <code
Expand All @@ -29036,8 +29062,17 @@ interface <dfn interface>HTMLSourceElement</dfn> : <span>HTMLElement</span> {
mutation</span> for the <code>img</code> element.</p></li>
</ol>

<p>The <code>source</code> <span data-x="html element removing steps">HTML element removing
steps</span>, given <var>removedNode</var> and <var>oldParent</var>, are:</p>
<p>The <code>source</code> <span>HTML element moving steps</span>, given <var>movedNode</var> and
<var>oldParent</var>, are:</p>

<ol>
<li><p>If <var>movedNode</var>'s next sibling was an <code>img</code> element and
<var>oldParent</var> is a <code>picture</code> element, then, count this as a <span
data-x="relevant mutations">relevant mutation</span> for the <code>img</code> element.</p></li>
</ol>

<p>The <code>source</code> <span>HTML element removing steps</span>, given <var>removedNode</var>
and <var>oldParent</var>, are:</p>

<ol>
<li><p>If <var>removedNode</var>'s next sibling was an <code>img</code> element and
Expand Down Expand Up @@ -29297,17 +29332,25 @@ interface <dfn interface>HTMLImageElement</dfn> : <span>HTMLElement</span> {
page layout from shifting around after the image loads.</p>
</div>

<p>The <code>img</code> <span data-x="html element insertion steps">HTML element insertion
steps</span>, given <var>insertedNode</var>, are:</p>
<p>The <code>img</code> <span>HTML element insertion steps</span>, given <var>insertedNode</var>,
are:</p>

<ol>
<li><p>If <var>insertedNode</var>'s parent is a <code>picture</code> element, then, count this as
a <span data-x="relevant mutations">relevant mutation</span> for
<var>insertedNode</var>.</p></li>
</ol>

<p>The <code>img</code> <span data-x="html element removing steps">HTML element removing
steps</span>, given <var>removedNode</var> and <var>oldParent</var>, are:</p>
<p>The <code>img</code> <span>HTML element moving steps</span>, given <var>movedNode</var> and
<var>oldParent</var>, are:</p>

<ol>
<li><p>If <var>oldParent</var> is a <code>picture</code> element, then, count this as a
<span data-x="relevant mutations">relevant mutation</span> for <var>movedNode</var>.</p></li>
</ol>

<p>The <code>img</code> <span>HTML element removing steps</span>, given <var>movedNode</var> and
<var>oldParent</var>, are:</p>

<ol>
<li><p>If <var>oldParent</var> is a <code>picture</code> element, then, count this as a
Expand Down Expand Up @@ -30807,9 +30850,9 @@ was an English &lt;a href="/wiki/Music_hall">music hall&lt;/a> singer, ...</code
<li><p>The element's <code data-x="attr-img-referrerpolicy">referrerpolicy</code> attribute's
state is changed.</p></li>

<li><p>The <code>img</code> or <code>source</code> <span>HTML element insertion steps</span> or
<span>HTML element removing steps</span> count the mutation as a <span data-x="relevant
mutations">relevant mutation</span>.</p></li>
<li><p>The <code>img</code> or <code>source</code> <span>HTML element insertion steps</span>,
<span>HTML element removing steps</span>, and <span>HTML element moving steps</span> count the
mutation as a <span data-x="relevant mutations">relevant mutation</span>.</p></li>

<li><p>The element's parent is a <code>picture</code> element and a <code>source</code> element
that is a previous sibling has its <code data-x="attr-source-srcset">srcset</code>, <code
Expand Down Expand Up @@ -36418,13 +36461,15 @@ interface <dfn interface>MediaError</dfn> {
other node is the node after <var>pointer</var>. Initially, let <var>pointer</var> be the position between the <var>candidate</var> node and the
next node, if there are any, or the end of the list, if it is the last node.</p>

<p>As nodes are <span data-x="concept-node-insert-ext">inserted</span> and <span
data-x="concept-node-remove-ext">removed</span> into the <span>media element</span>,
<p>As nodes are <span data-x="concept-node-insert-ext">inserted</span>, <span
data-x="concept-node-remove-ext">removed</span>, and <span
data-x="concept-node-move-ext">moved</span> into the <span>media element</span>,
<var>pointer</var> must be updated as follows:</p>

<dl>
<dt>If a new node is <span data-x="concept-node-insert-ext">inserted</span> between the two
nodes that define <var>pointer</var></dt>
<dt>If a new node is <span data-x="concept-node-insert-ext">inserted</span> or <span
data-x="concept-node-move-ext">moved</span> between the two nodes that define
<var>pointer</var></dt>

<dd>Let <var>pointer</var> be the point between the node before <var>pointer</var> and the new node. In other words, insertions at <var>pointer</var> go after <var>pointer</var>.</dd>

Expand Down Expand Up @@ -53462,6 +53507,17 @@ interface <dfn interface>HTMLSelectElement</dfn> : <span>HTMLElement</span> {
<code>select</code> element's <span>selectedness setting algorithm</span>.</p></li>
</ol>

<p>The <code>option</code> <span>HTML element moving steps</span>, given <var>movedNode</var> and
<var>oldParent</var>, are:</p>

<ol>
<li><p>Run the <code>option</code> <span>HTML element removing steps</span> given
<var>movedNode</var> and <var>oldParent</var>.</p></li>

<li><p>Run the <code>option</code> <span>HTML element insertion steps</span> given
<var>movedNode</var>.</p></li>
</ol>

<p>The <code>optgroup</code> <span data-x="html element removing steps">HTML element removing
steps</span>, given <var>removedNode</var> and <var>oldParent</var>, are:</p>

Expand All @@ -53471,6 +53527,14 @@ interface <dfn interface>HTMLSelectElement</dfn> : <span>HTMLElement</span> {
algorithm</span>.</p></li>
</ol>

<p>The <code>optgroup</code> <span>HTML element moving steps</span>, given <var>movedNode</var>
and <var>oldParent</var>, are:</p>

<ol>
<li><p>Run the <code>optgroup</code> <span>HTML element removing steps</span> given
<var>movedNode</var> and <var>oldParent</var>.</p></li>
</ol>

<p>If an <code>option</code> element in the <span data-x="concept-select-option-list">list of
options</span> <dfn data-x="ask for a reset">asks for a reset</dfn>, then run that
<code>select</code> element's <span>selectedness setting algorithm</span>.</p>
Expand Down Expand Up @@ -55970,12 +56034,11 @@ interface <dfn interface>HTMLLegendElement</dfn> : <span>HTMLElement</span> {
<code data-x="attr-fae-form">form</code> attribute and an element with an <span
data-x="concept-id">ID</span> is <span data-x="node is inserted into a document">inserted
into</span> or <span data-x="node is removed from a document">removed from</span> the
<code>Document</code>, then the user agent must <span>reset the form owner</span> of that
<span>form-associated element</span>.</p>
<code>Document</code>, or its <span>HTML element moving steps</span> are run, then the user agent
must <span>reset the form owner</span> of that <span>form-associated element</span>.</p>

<p class="note">The form owner is also reset by the HTML Standard's <span
data-x="concept-node-insert-ext">insertion steps</span> and <span
data-x="concept-node-remove-ext">removing steps</span>.</p>
<p class="note">The form owner is also reset by the <span>HTML element insertion steps</span>,
<span>HTML element removing steps</span>, and <span>HTML element moving steps</span>.</p>

<p>To <dfn>reset the form owner</dfn> of a <span>form-associated element</span>
<var>element</var>:</p>
Expand Down Expand Up @@ -72128,6 +72191,7 @@ document.body.append(parent);
data-x="concept-custom-element-definition-lifecycle-callbacks">lifecycle callbacks</dfn></dt>
<dd>A map, whose keys are the strings "<code data-x="">connectedCallback</code>",
"<code data-x="">disconnectedCallback</code>", "<code data-x="">adoptedCallback</code>",
"<code data-x="">connectedMoveCallback</code>",
"<code data-x="">attributeChangedCallback</code>",
"<code data-x="">formAssociatedCallback</code>",
"<code data-x="">formDisabledCallback</code>",
Expand Down Expand Up @@ -72810,7 +72874,10 @@ customElements.define("x-foo", class extends HTMLElement {
<li><p>When it <span>becomes disconnected</span>, its <code data-x="">disconnectedCallback</code>
is called, with no arguments.</p></li>

<li><p>When it is <span data-x="concept-node-adopt">adopted</span> into a new document, its <code
<li><p>When it is <span data-x="concept-node-move-ext">moved</span>, its <code
data-x="">connectedMoveCallback</code> is called, with no arguments.</p></li>

Choose a reason for hiding this comment

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

Shouldn't callback get the old subtree owner as an argument, so that one can know easily where one was.

Copy link
Contributor

Choose a reason for hiding this comment

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

This would require holding a reference to the old tree which might be gone by the time the CE callbacks are fired. Perhaps we can consider this as a future enhancement? I think we should be able to do that in a compatible way.


<li><p>When it is <span data-x="concept-node-adopt">adopted</span> into a new document, its <code
data-x="">adoptedCallback</code> is called, given the old document and new document as
arguments.</p></li>

Expand Down Expand Up @@ -72952,6 +73019,35 @@ customElements.define("x-foo", class extends HTMLElement {
data-x="concept-custom-element-definition-lifecycle-callbacks">lifecycle callbacks</span> with
key <var>callbackName</var>.</p></li>

<li>
<p>If <var>callbackName</var> is "<code data-x="">connectedMoveCallback</code>" and
<var>callback</var> is null, then:

<ol>
<li><p>Let <var>disconnectedCallback</var> be the value of the entry in <var>definition</var>'s
<span data-x="concept-custom-element-definition-lifecycle-callbacks">lifecycle callbacks</span>
with key "<code data-x="">disconnectedCallback</code>".</p></li>

<li><p>Let <var>connectedCallback</var> be the value of the entry in <var>definition</var>'s
<span data-x="concept-custom-element-definition-lifecycle-callbacks">lifecycle callbacks</span>
with key "<code data-x="">connectedCallback</code>".</p></li>

<li><p>If <var>connectedCallback</var> and <var>disconnectedCallback</var> are null, then
return.</p></li>

<li>
<p>Set <var>callback</var> to the following steps:</p>
<ol>
<li><p>If <var>disconnectedCallback</var> is not null, then call
<var>disconnectedCallback</var> with no arguments.</p></li>

<li><p>If <var>connectedCallback</var> is not null, then call
<var>connectedCallback</var> with no arguments.</p></li>
</ol>
</li>
</ol>
</li>

<li><p>If <var>callback</var> is null, then return.</p></li>

<li>
Expand Down