LibWeb: Reset form association when any element with an ID changes
When an element with an ID is added to or removed from the DOM, or if an ID is added, removed, or changed, then we must reset the form owner of all form-associated elements who have a form attribute. We do this in 2 steps, using the DOM document as the messenger to handle these changes: 1. All form-associated elements with a form attribute are stored on the document. If the form attribute is removed, the element is removed from that list as well. 2. When a DOM element with an ID undergoes any of the aforementioned changes, it notifies the document of the change. The document then forwards that change to the stored form-associated elements.
This commit is contained in:
parent
960dcf0e56
commit
a17074422e
Notes:
sideshowbarker
2024-07-18 03:23:00 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/a17074422e Pull-request: https://github.com/SerenityOS/serenity/pull/23032 Issue: https://github.com/SerenityOS/serenity/issues/22537 Issue: https://github.com/SerenityOS/serenity/issues/22705 Issue: https://github.com/SerenityOS/serenity/issues/23008 Reviewed-by: https://github.com/ADKaster ✅ Reviewed-by: https://github.com/Lubrsi
8 changed files with 97 additions and 6 deletions
|
@ -1,4 +1,4 @@
|
|||
== Elements and Names ==
|
||||
== Elements and Names ==
|
||||
formy.length: 12
|
||||
elements.length: 12
|
||||
elements[0] === form.foo
|
||||
|
@ -38,3 +38,6 @@ elements in changeForFormAttribute: 1
|
|||
elements in changeForFormAttribute: 0
|
||||
elements in changeForFormAttribute: 1
|
||||
elements in changeForFormAttribute: 0
|
||||
== Form element appears after a form-associated element ==
|
||||
elements in formAfterInput: 1
|
||||
typeof formAfterInput.inputBeforeForm: object
|
||||
|
|
|
@ -63,6 +63,9 @@
|
|||
<form id="changeForFormAttribute"></form>
|
||||
<input id="changeForFormAttributeInput" type="text" name="changeForFormAttribute" />
|
||||
|
||||
<input id="inputBeforeForm" type="text" form="formAfterInput" />
|
||||
<form id="formAfterInput"></form>
|
||||
|
||||
<script src="../include.js"></script>
|
||||
<script>
|
||||
test(() => {
|
||||
|
@ -138,5 +141,10 @@
|
|||
|
||||
changeForFormAttributeInput.removeAttribute("form");
|
||||
println(`elements in changeForFormAttribute: ${changeForFormAttribute.elements.length}`);
|
||||
|
||||
println("== Form element appears after a form-associated element ==");
|
||||
let formAfterInput = document.getElementById("formAfterInput");
|
||||
println(`elements in formAfterInput: ${formAfterInput.elements.length}`);
|
||||
println(`typeof formAfterInput.inputBeforeForm: ${typeof formAfterInput.inputBeforeForm}`);
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -449,6 +449,9 @@ void Document::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(timeline);
|
||||
|
||||
visitor.visit(m_list_of_available_images);
|
||||
|
||||
for (auto* form_associated_element : m_form_associated_elements_with_form_attribute)
|
||||
visitor.visit(form_associated_element->form_associated_element_to_html_element());
|
||||
}
|
||||
|
||||
// https://w3c.github.io/selection-api/#dom-document-getselection
|
||||
|
@ -3683,4 +3686,28 @@ void Document::append_pending_animation_event(Web::DOM::Document::PendingAnimati
|
|||
m_pending_animation_event_queue.append(event);
|
||||
}
|
||||
|
||||
void Document::element_id_changed(Badge<DOM::Element>)
|
||||
{
|
||||
for (auto* form_associated_element : m_form_associated_elements_with_form_attribute)
|
||||
form_associated_element->element_id_changed({});
|
||||
}
|
||||
|
||||
void Document::element_with_id_was_added_or_removed(Badge<DOM::Element>)
|
||||
{
|
||||
for (auto* form_associated_element : m_form_associated_elements_with_form_attribute)
|
||||
form_associated_element->element_with_id_was_added_or_removed({});
|
||||
}
|
||||
|
||||
void Document::add_form_associated_element_with_form_attribute(HTML::FormAssociatedElement& form_associated_element)
|
||||
{
|
||||
m_form_associated_elements_with_form_attribute.append(&form_associated_element);
|
||||
}
|
||||
|
||||
void Document::remove_form_associated_element_with_form_attribute(HTML::FormAssociatedElement& form_associated_element)
|
||||
{
|
||||
m_form_associated_elements_with_form_attribute.remove_all_matching([&](auto* element) {
|
||||
return element == &form_associated_element;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -554,6 +554,12 @@ public:
|
|||
JS::GCPtr<HTML::SessionHistoryEntry> latest_entry() const { return m_latest_entry; }
|
||||
void set_latest_entry(JS::GCPtr<HTML::SessionHistoryEntry> e) { m_latest_entry = e; }
|
||||
|
||||
void element_id_changed(Badge<DOM::Element>);
|
||||
void element_with_id_was_added_or_removed(Badge<DOM::Element>);
|
||||
|
||||
void add_form_associated_element_with_form_attribute(HTML::FormAssociatedElement&);
|
||||
void remove_form_associated_element_with_form_attribute(HTML::FormAssociatedElement&);
|
||||
|
||||
protected:
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
@ -775,6 +781,8 @@ private:
|
|||
|
||||
// https://html.spec.whatwg.org/multipage/browsing-the-web.html#scripts-may-run-for-the-newly-created-document
|
||||
bool m_ready_to_run_scripts { false };
|
||||
|
||||
Vector<HTML::FormAssociatedElement*> m_form_associated_elements_with_form_attribute;
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
@ -465,6 +465,8 @@ void Element::attribute_changed(FlyString const& name, Optional<String> const& v
|
|||
m_id = {};
|
||||
else
|
||||
m_id = value_or_empty;
|
||||
|
||||
document().element_id_changed({});
|
||||
} else if (name == HTML::AttributeNames::name) {
|
||||
if (!value.has_value())
|
||||
m_name = {};
|
||||
|
@ -1026,6 +1028,22 @@ int Element::client_height() const
|
|||
return paintable_box()->absolute_padding_box_rect().height().to_int();
|
||||
}
|
||||
|
||||
void Element::inserted()
|
||||
{
|
||||
Base::inserted();
|
||||
|
||||
if (m_id.has_value())
|
||||
document().element_with_id_was_added_or_removed({});
|
||||
}
|
||||
|
||||
void Element::removed_from(Node* node)
|
||||
{
|
||||
Base::removed_from(node);
|
||||
|
||||
if (m_id.has_value())
|
||||
document().element_with_id_was_added_or_removed({});
|
||||
}
|
||||
|
||||
void Element::children_changed()
|
||||
{
|
||||
Node::children_changed();
|
||||
|
|
|
@ -375,6 +375,8 @@ protected:
|
|||
Element(Document&, DOM::QualifiedName);
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
||||
virtual void inserted() override;
|
||||
virtual void removed_from(Node*) override;
|
||||
virtual void children_changed() override;
|
||||
virtual i32 default_tab_index_value() const;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/FormAssociatedElement.h>
|
||||
#include <LibWeb/HTML/HTMLButtonElement.h>
|
||||
#include <LibWeb/HTML/HTMLFieldSetElement.h>
|
||||
|
@ -73,24 +74,45 @@ void FormAssociatedElement::form_node_was_removed()
|
|||
}
|
||||
|
||||
// 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&)
|
||||
void FormAssociatedElement::form_node_attribute_changed(FlyString const& name, Optional<String> const& value)
|
||||
{
|
||||
// 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 (name == HTML::AttributeNames::form) {
|
||||
auto& html_element = form_associated_element_to_html_element();
|
||||
|
||||
if (value.has_value())
|
||||
html_element.document().add_form_associated_element_with_form_attribute(*this);
|
||||
else
|
||||
html_element.document().remove_form_associated_element_with_form_attribute(*this);
|
||||
|
||||
reset_form_owner();
|
||||
}
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-4
|
||||
void FormAssociatedElement::element_id_changed(Badge<DOM::Document>)
|
||||
{
|
||||
// When a listed form-associated element has a form attribute and the ID of any of the elements in the tree changes,
|
||||
// then the user agent must reset the form owner of that form-associated element.
|
||||
VERIFY(form_associated_element_to_html_element().has_attribute(HTML::AttributeNames::form));
|
||||
reset_form_owner();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#association-of-controls-and-forms:category-listed-5
|
||||
void FormAssociatedElement::element_with_id_was_added_or_removed(Badge<DOM::Document>)
|
||||
{
|
||||
// When a listed form-associated element has a form attribute and an element with an ID is inserted into or removed
|
||||
// from the Document, then the user agent must reset the form owner of that form-associated element.
|
||||
VERIFY(form_associated_element_to_html_element().has_attribute(HTML::AttributeNames::form));
|
||||
reset_form_owner();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#reset-the-form-owner
|
||||
void FormAssociatedElement::reset_form_owner()
|
||||
{
|
||||
auto& html_element = form_associated_element_to_html_element();
|
||||
|
||||
// Although these aren't in the "reset form owner" algorithm, these here as they are triggers for this algorithm:
|
||||
// FIXME: When a listed form-associated element has a form attribute and the ID of any of the elements in the tree changes, then the user agent must reset the form owner of that form-associated element.
|
||||
// FIXME: When a listed form-associated element has a form attribute and an element with an ID is inserted into or removed from the Document, then the user agent must reset the form owner of that form-associated element.
|
||||
|
||||
// 1. Unset element's parser inserted flag.
|
||||
m_parser_inserted = false;
|
||||
|
||||
|
|
|
@ -55,6 +55,9 @@ public:
|
|||
|
||||
void set_form(HTMLFormElement*);
|
||||
|
||||
void element_id_changed(Badge<DOM::Document>);
|
||||
void element_with_id_was_added_or_removed(Badge<DOM::Document>);
|
||||
|
||||
bool enabled() const;
|
||||
|
||||
void set_parser_inserted(Badge<HTMLParser>);
|
||||
|
|
Loading…
Add table
Reference in a new issue