LibWeb/HTML: Ignore form owner if earlier same-ID element isn't a form

Only set form owner when there is no non-form element with the same ID
as the control's form attribute earlier in tree order.
This commit is contained in:
Glenn Skrzypczak 2025-11-23 21:01:57 +01:00 committed by Jelle Raaijmakers
parent 405e270583
commit a0dbae02c8
Notes: github-actions[bot] 2025-11-24 08:53:14 +00:00
3 changed files with 341 additions and 3 deletions

View file

@ -187,9 +187,10 @@ void FormAssociatedElement::reset_form_owner()
if (is_listed() && html_element.has_attribute(HTML::AttributeNames::form) && html_element.is_connected()) {
// 1. If the first element in element's tree, in tree order, to have an ID that is identical to element's form content attribute's value, is a form element, then associate the element with that form element.
auto form_value = html_element.attribute(HTML::AttributeNames::form);
html_element.root().for_each_in_inclusive_subtree_of_type<HTMLFormElement>([this, &form_value](HTMLFormElement& form_element) {
if (form_element.id() == form_value) {
set_form(&form_element);
html_element.root().for_each_in_inclusive_subtree_of_type<HTMLElement>([this, &form_value](auto& element) {
if (element.id() == form_value) {
if (is<HTMLFormElement>(element))
set_form(as<HTMLFormElement>(&element));
return TraversalDecision::Break;
}

View file

@ -0,0 +1,104 @@
Harness status: OK
Found 99 tests
99 Pass
Pass Tests for parser inserted controls
Pass [BUTTON] Basic form association - control with no form attribute is associated with ancestor
Pass [BUTTON] Form owner is reset to null when control's form attribute is set to an ID that does not exist in the document
Pass [BUTTON] Control whose form attribute is an empty string has no form owner
Pass [BUTTON] Control whose form attribute is an empty string has no form owner even when form with empty attribute is present
Pass [BUTTON] Control's form attribute must be a case sensitive match for the form's id
Pass [BUTTON] Setting the form attribute of a control to the id of a non-ancestor form works
Pass [BUTTON] Removing form id from a control resets the form owner to ancestor
Pass [BUTTON] Removing the form owner of a control with form attribute resets the form owner to null
Pass [BUTTON] Changing form attibute of control resets form owner to correct form
Pass [BUTTON] Moving a control with form attribute within the document does not change the form owner
Pass [BUTTON] When the id of a non-ancestor form changes from not being a match for the form attribute to being a match, the control's form owner is reset
Pass [BUTTON] When form element with same ID as the control's form attribute is inserted earlier in tree order, the form owner is changed to the inserted form
Pass [BUTTON] When non-form element with same ID as the control's form attribute is inserted earlier in tree order, the control does not have a form owner
Pass [BUTTON] A control that is not in the document but has the form attribute set is associated with the nearest ancestor form if one exists
Pass [FIELDSET] Basic form association - control with no form attribute is associated with ancestor
Pass [FIELDSET] Form owner is reset to null when control's form attribute is set to an ID that does not exist in the document
Pass [FIELDSET] Control whose form attribute is an empty string has no form owner
Pass [FIELDSET] Control whose form attribute is an empty string has no form owner even when form with empty attribute is present
Pass [FIELDSET] Control's form attribute must be a case sensitive match for the form's id
Pass [FIELDSET] Setting the form attribute of a control to the id of a non-ancestor form works
Pass [FIELDSET] Removing form id from a control resets the form owner to ancestor
Pass [FIELDSET] Removing the form owner of a control with form attribute resets the form owner to null
Pass [FIELDSET] Changing form attibute of control resets form owner to correct form
Pass [FIELDSET] Moving a control with form attribute within the document does not change the form owner
Pass [FIELDSET] When the id of a non-ancestor form changes from not being a match for the form attribute to being a match, the control's form owner is reset
Pass [FIELDSET] When form element with same ID as the control's form attribute is inserted earlier in tree order, the form owner is changed to the inserted form
Pass [FIELDSET] When non-form element with same ID as the control's form attribute is inserted earlier in tree order, the control does not have a form owner
Pass [FIELDSET] A control that is not in the document but has the form attribute set is associated with the nearest ancestor form if one exists
Pass [INPUT] Basic form association - control with no form attribute is associated with ancestor
Pass [INPUT] Form owner is reset to null when control's form attribute is set to an ID that does not exist in the document
Pass [INPUT] Control whose form attribute is an empty string has no form owner
Pass [INPUT] Control whose form attribute is an empty string has no form owner even when form with empty attribute is present
Pass [INPUT] Control's form attribute must be a case sensitive match for the form's id
Pass [INPUT] Setting the form attribute of a control to the id of a non-ancestor form works
Pass [INPUT] Removing form id from a control resets the form owner to ancestor
Pass [INPUT] Removing the form owner of a control with form attribute resets the form owner to null
Pass [INPUT] Changing form attibute of control resets form owner to correct form
Pass [INPUT] Moving a control with form attribute within the document does not change the form owner
Pass [INPUT] When the id of a non-ancestor form changes from not being a match for the form attribute to being a match, the control's form owner is reset
Pass [INPUT] When form element with same ID as the control's form attribute is inserted earlier in tree order, the form owner is changed to the inserted form
Pass [INPUT] When non-form element with same ID as the control's form attribute is inserted earlier in tree order, the control does not have a form owner
Pass [INPUT] A control that is not in the document but has the form attribute set is associated with the nearest ancestor form if one exists
Pass [OBJECT] Basic form association - control with no form attribute is associated with ancestor
Pass [OBJECT] Form owner is reset to null when control's form attribute is set to an ID that does not exist in the document
Pass [OBJECT] Control whose form attribute is an empty string has no form owner
Pass [OBJECT] Control whose form attribute is an empty string has no form owner even when form with empty attribute is present
Pass [OBJECT] Control's form attribute must be a case sensitive match for the form's id
Pass [OBJECT] Setting the form attribute of a control to the id of a non-ancestor form works
Pass [OBJECT] Removing form id from a control resets the form owner to ancestor
Pass [OBJECT] Removing the form owner of a control with form attribute resets the form owner to null
Pass [OBJECT] Changing form attibute of control resets form owner to correct form
Pass [OBJECT] Moving a control with form attribute within the document does not change the form owner
Pass [OBJECT] When the id of a non-ancestor form changes from not being a match for the form attribute to being a match, the control's form owner is reset
Pass [OBJECT] When form element with same ID as the control's form attribute is inserted earlier in tree order, the form owner is changed to the inserted form
Pass [OBJECT] When non-form element with same ID as the control's form attribute is inserted earlier in tree order, the control does not have a form owner
Pass [OBJECT] A control that is not in the document but has the form attribute set is associated with the nearest ancestor form if one exists
Pass [OUTPUT] Basic form association - control with no form attribute is associated with ancestor
Pass [OUTPUT] Form owner is reset to null when control's form attribute is set to an ID that does not exist in the document
Pass [OUTPUT] Control whose form attribute is an empty string has no form owner
Pass [OUTPUT] Control whose form attribute is an empty string has no form owner even when form with empty attribute is present
Pass [OUTPUT] Control's form attribute must be a case sensitive match for the form's id
Pass [OUTPUT] Setting the form attribute of a control to the id of a non-ancestor form works
Pass [OUTPUT] Removing form id from a control resets the form owner to ancestor
Pass [OUTPUT] Removing the form owner of a control with form attribute resets the form owner to null
Pass [OUTPUT] Changing form attibute of control resets form owner to correct form
Pass [OUTPUT] Moving a control with form attribute within the document does not change the form owner
Pass [OUTPUT] When the id of a non-ancestor form changes from not being a match for the form attribute to being a match, the control's form owner is reset
Pass [OUTPUT] When form element with same ID as the control's form attribute is inserted earlier in tree order, the form owner is changed to the inserted form
Pass [OUTPUT] When non-form element with same ID as the control's form attribute is inserted earlier in tree order, the control does not have a form owner
Pass [OUTPUT] A control that is not in the document but has the form attribute set is associated with the nearest ancestor form if one exists
Pass [SELECT] Basic form association - control with no form attribute is associated with ancestor
Pass [SELECT] Form owner is reset to null when control's form attribute is set to an ID that does not exist in the document
Pass [SELECT] Control whose form attribute is an empty string has no form owner
Pass [SELECT] Control whose form attribute is an empty string has no form owner even when form with empty attribute is present
Pass [SELECT] Control's form attribute must be a case sensitive match for the form's id
Pass [SELECT] Setting the form attribute of a control to the id of a non-ancestor form works
Pass [SELECT] Removing form id from a control resets the form owner to ancestor
Pass [SELECT] Removing the form owner of a control with form attribute resets the form owner to null
Pass [SELECT] Changing form attibute of control resets form owner to correct form
Pass [SELECT] Moving a control with form attribute within the document does not change the form owner
Pass [SELECT] When the id of a non-ancestor form changes from not being a match for the form attribute to being a match, the control's form owner is reset
Pass [SELECT] When form element with same ID as the control's form attribute is inserted earlier in tree order, the form owner is changed to the inserted form
Pass [SELECT] When non-form element with same ID as the control's form attribute is inserted earlier in tree order, the control does not have a form owner
Pass [SELECT] A control that is not in the document but has the form attribute set is associated with the nearest ancestor form if one exists
Pass [TEXTAREA] Basic form association - control with no form attribute is associated with ancestor
Pass [TEXTAREA] Form owner is reset to null when control's form attribute is set to an ID that does not exist in the document
Pass [TEXTAREA] Control whose form attribute is an empty string has no form owner
Pass [TEXTAREA] Control whose form attribute is an empty string has no form owner even when form with empty attribute is present
Pass [TEXTAREA] Control's form attribute must be a case sensitive match for the form's id
Pass [TEXTAREA] Setting the form attribute of a control to the id of a non-ancestor form works
Pass [TEXTAREA] Removing form id from a control resets the form owner to ancestor
Pass [TEXTAREA] Removing the form owner of a control with form attribute resets the form owner to null
Pass [TEXTAREA] Changing form attibute of control resets form owner to correct form
Pass [TEXTAREA] Moving a control with form attribute within the document does not change the form owner
Pass [TEXTAREA] When the id of a non-ancestor form changes from not being a match for the form attribute to being a match, the control's form owner is reset
Pass [TEXTAREA] When form element with same ID as the control's form attribute is inserted earlier in tree order, the form owner is changed to the inserted form
Pass [TEXTAREA] When non-form element with same ID as the control's form attribute is inserted earlier in tree order, the control does not have a form owner
Pass [TEXTAREA] A control that is not in the document but has the form attribute set is associated with the nearest ancestor form if one exists

View file

@ -0,0 +1,233 @@
<!DOCTYPE html>
<html>
<head>
<script src="../../../../resources/testharness.js"></script>
<script src="../../../../resources/testharnessreport.js"></script>
</head>
<body>
<div>
<form id="f1"></form>
<form id="f2">
<input id="i1" />
<input id="i2" form="f1" />
<input id="i3" />
</form>
<script>
test(function() {
var i1 = document.getElementById("i1");
var i2 = document.getElementById("i2");
var i3 = document.getElementById("i3");
var f1 = document.getElementById("f1");
var f2 = document.getElementById("f2");
assert_equals(i1.form, f2,
"i1 must be associated with f2 by the parser");
assert_equals(i2.form, f1,
"i2 is not associated with f2 by the parser " +
"since it has the form attribute set to f1");
f1.appendChild(i1);
i3.setAttribute("form", "f1");
assert_equals(i1.form, f1,
"i1's form owner must be reset when parent changes");
assert_equals(i3.form, f1,
"i3's form owner must be reset when the form" +
"attribute is set");
assert_equals(i2.form, f1);
}, "Tests for parser inserted controls");
</script>
</div>
<div id="placeholder">
</div>
<script>
var reassociateableElements = [
"button",
"fieldset",
"input",
"object",
"output",
"select",
"textarea",
];
var form1 = null;
var form2 = null;
var placeholder = document.getElementById("placeholder");
reassociateableElements.forEach(function(localName) {
function testControl(test_, desc) {
test(function() {
var control = document.createElement(localName);
while(placeholder.firstChild)
placeholder.removeChild(placeholder.firstChild);
form1 = document.createElement("form");
form2 = document.createElement("form");
form1.id = "form1";
form2.id = "form2";
placeholder.appendChild(form1);
placeholder.appendChild(form2);
test_.call(control);
}, "[" + localName.toUpperCase() + "] " + desc);
}
testControl(function() {
form1.appendChild(this);
assert_equals(this.form, form1);
}, "Basic form association - control with no form attribute is associated with ancestor");
testControl(function() {
this.setAttribute("form", "form1");
form1.appendChild(this);
assert_equals(this.form, form1);
form1.id = "form-one";
assert_equals(this.form, null);
}, "Form owner is reset to null when control's form attribute is set to an ID " +
"that does not exist in the document");
testControl(function() {
this.setAttribute("form", "");
form1.appendChild(this);
assert_equals(this.form, null);
}, "Control whose form attribute is an empty string has no form owner");
testControl(function() {
form1.id = "";
this.setAttribute("form", "");
form1.appendChild(this);
assert_equals(this.form, null);
}, "Control whose form attribute is an empty string has no form owner " +
"even when form with empty attribute is present");
testControl(function() {
form1.id = "FORM1";
this.setAttribute("form", "form1");
form1.appendChild(this);
assert_equals(this.form, null);
}, "Control's form attribute must be a case sensitive match for the form's id");
testControl(function() {
form1.appendChild(this);
assert_equals(this.form, form1);
this.setAttribute("form", "form2");
assert_equals(this.form, form2);
}, "Setting the form attribute of a control to the id of a non-ancestor form works");
testControl(function() {
this.setAttribute("form", "form1");
form2.appendChild(this);
assert_equals(this.form, form1);
this.removeAttribute("form");
assert_equals(this.form, form2);
}, "Removing form id from a control resets the form owner to ancestor");
testControl(function() {
this.setAttribute("form", "form1");
form2.appendChild(this);
assert_equals(this.form, form1);
placeholder.removeChild(form1);
assert_equals(this.form, null);
}, "Removing the form owner of a control with form attribute resets " +
"the form owner to null");
testControl(function() {
var form3 = document.createElement("form");
form3.id = "form3";
placeholder.appendChild(form3);
form3.appendChild(this);
assert_equals(this.form, form3);
this.setAttribute("form", "form2");
assert_equals(this.form, form2);
this.setAttribute("form", "form1");
assert_equals(this.form, form1);
}, "Changing form attibute of control resets form owner to correct form");
testControl(function() {
this.setAttribute("form", "form1");
var form3 = document.createElement("form");
form3.id = "form3";
placeholder.appendChild(form3);
placeholder.appendChild(this);
assert_equals(this.form, form1);
form1.appendChild(this);
assert_equals(this.form, form1);
form2.appendChild(this);
assert_equals(this.form, form1);
}, "Moving a control with form attribute within the document " +
"does not change the form owner");
testControl(function() {
form1.id = "form-one";
this.setAttribute("form", "form1");
form2.appendChild(this);
assert_equals(this.form, null);
form1.id = "form1";
assert_equals(this.form, form1);
}, "When the id of a non-ancestor form changes from not being a match for the " +
"form attribute to being a match, the control's form owner is reset");
testControl(function() {
this.setAttribute("form", "form1");
form1.appendChild(this);
assert_equals(this.form, form1);
form2.id = "form1";
form1.parentNode.insertBefore(form2, form1);
assert_equals(this.form, form2);
form2.parentNode.removeChild(form2);
assert_equals(this.form, form1);
form1.parentNode.appendChild(form2);
assert_equals(this.form, form1);
}, "When form element with same ID as the control's form attribute is inserted " +
"earlier in tree order, the form owner is changed to the inserted form");
testControl(function() {
this.setAttribute("form", "form1");
form2.appendChild(this);
assert_equals(this.form, form1);
var span = document.createElement("span");
span.id = "form1";
form1.parentNode.insertBefore(span, form1);
assert_equals(this.form, null);
form1.parentNode.appendChild(span);
assert_equals(this.form, form1);
}, "When non-form element with same ID as the control's form attribute is " +
"inserted earlier in tree order, the control does not have a form owner");
testControl(function() {
this.setAttribute("form", "form1");
form1.appendChild(this);
assert_equals(this.form, form1);
form1.parentNode.removeChild(form1);
assert_equals(this.form, form1);
}, "A control that is not in the document but has the form attribute set " +
"is associated with the nearest ancestor form if one exists");
});
</script>
</body>
</html>