LibWeb: Implement 'State-preserving atomic move integration'

This was recently added to both the HTML and DOM specifications,
introducing the new moveBefore DOM API, as well as the new internal
'removing steps'.

See:

 * 432e8fb
 * eaf2ac7
This commit is contained in:
Shannon Booth 2025-03-08 12:45:26 +13:00 committed by Andrew Kaster
commit 31a3bc3681
Notes: github-actions[bot] 2025-04-26 14:46:43 +00:00
39 changed files with 1383 additions and 12 deletions

View file

@ -15,6 +15,7 @@ namespace Web::HTML::CustomElementReactionNames {
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(connectedCallback) \
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(disconnectedCallback) \
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(adoptedCallback) \
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(connectedMoveCallback) \
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(attributeChangedCallback) \
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(formAssociatedCallback) \
__ENUMERATE_CUSTOM_ELEMENT_REACTION_NAME(formDisabledCallback) \

View file

@ -199,14 +199,15 @@ JS::ThrowCompletionOr<void> CustomElementRegistry::define(String const& name, We
auto& prototype = prototype_value.as_object();
// 3. Let lifecycleCallbacks be the ordered map «[ "connectedCallback" → null, "disconnectedCallback" → null, "adoptedCallback" → null,
// "attributeChangedCallback" → null ]».
// "connectedMoveCallback" → null, "attributeChangedCallback" → null ]».
lifecycle_callbacks.set(CustomElementReactionNames::connectedCallback, {});
lifecycle_callbacks.set(CustomElementReactionNames::disconnectedCallback, {});
lifecycle_callbacks.set(CustomElementReactionNames::adoptedCallback, {});
lifecycle_callbacks.set(CustomElementReactionNames::connectedMoveCallback, {});
lifecycle_callbacks.set(CustomElementReactionNames::attributeChangedCallback, {});
// 4. For each callbackName of the keys of lifecycleCallbacks:
for (auto const& callback_name : { CustomElementReactionNames::connectedCallback, CustomElementReactionNames::disconnectedCallback, CustomElementReactionNames::adoptedCallback, CustomElementReactionNames::attributeChangedCallback }) {
for (auto const& callback_name : { CustomElementReactionNames::connectedCallback, CustomElementReactionNames::disconnectedCallback, CustomElementReactionNames::adoptedCallback, CustomElementReactionNames::connectedMoveCallback, CustomElementReactionNames::attributeChangedCallback }) {
// 1. Let callbackValue be ? Get(prototype, callbackName).
auto callback_value = TRY(prototype.get(callback_name));

View file

@ -116,6 +116,14 @@ void FormAssociatedElement::form_node_was_removed()
reset_form_owner();
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:attr-fae-form-2
void FormAssociatedElement::form_node_was_moved()
{
// When a listed form-associated element's form attribute is set, changed, or removed, then the user agent must reset the form owner of that element.
if (m_form && &form_associated_element_to_html_element().root() != &m_form->root())
reset_form_owner();
}
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-3
void FormAssociatedElement::form_node_attribute_changed(FlyString const& name, Optional<String> const& value)
{

View file

@ -45,6 +45,13 @@ private:
form_associated_element_was_removed(old_parent); \
} \
\
virtual void moved_from(GC::Ptr<DOM::Node> old_parent) override \
{ \
ElementBaseClass::moved_from(old_parent); \
form_node_was_moved(); \
form_associated_element_was_moved(old_parent); \
} \
\
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override \
{ \
ElementBaseClass::attribute_changed(name, old_value, value, namespace_); \
@ -137,10 +144,12 @@ protected:
virtual void form_associated_element_was_inserted() { }
virtual void form_associated_element_was_removed(DOM::Node*) { }
virtual void form_associated_element_was_moved(GC::Ptr<DOM::Node>) { }
virtual void form_associated_element_attribute_changed(FlyString const&, Optional<String> const&, Optional<FlyString> const&) { }
void form_node_was_inserted();
void form_node_was_removed();
void form_node_was_moved();
void form_node_attribute_changed(FlyString const&, Optional<String> const&);
private:

View file

@ -48,7 +48,16 @@ void HTMLSourceElement::inserted()
// count this as a relevant mutation for child.
}
// https://html.spec.whatwg.org/multipage/embedded-content.html#the-source-element:html-element-removing-steps
// https://html.spec.whatwg.org/multipage/embedded-content.html#the-source-element:the-source-element-17
void HTMLSourceElement::moved_from(GC::Ptr<DOM::Node> old_parent)
{
Base::moved_from(old_parent);
// FIXME: 1. If oldParent is a picture element, then for each child of oldParent's children, if child is an img
// element, then count this as a relevant mutation for child.
}
// https://html.spec.whatwg.org/multipage/embedded-content.html#the-source-element:the-source-element-18
void HTMLSourceElement::removed_from(DOM::Node* old_parent, DOM::Node& old_root)
{
// The source HTML element removing steps, given removedNode and oldParent, are:

View file

@ -24,6 +24,7 @@ private:
virtual void inserted() override;
virtual void removed_from(DOM::Node* old_parent, DOM::Node& old_root) override;
virtual void moved_from(GC::Ptr<Node> old_parent) override;
};
}