Skip to content

Commit

Permalink
take care of referencing form elements
Browse files Browse the repository at this point in the history
  • Loading branch information
rbri committed Oct 2, 2023
1 parent 0f4ec81 commit 00ce1ab
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 41 deletions.
4 changes: 4 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

<body>
<release version="3.6.0" date="September xx, 2023" description="Firefox 116, Chrome/Edge 116, htmx 1.9.5, htmx 1.9.6, Bugfixes">
<action type="fix" dev="rbri">
Form elements are all elements that belong to a form; not only the child elements. We now
take care of elements that use the form attribute to specify the associated form in many more places.
</action>
<action type="add" dev="Lai Quang Duong">
The URLSearchParams ctor now supports also a sequence of name-value string pairs,
or any object with an iterator that produces a sequence of string pairs,
Expand Down
75 changes: 46 additions & 29 deletions src/main/java/org/htmlunit/html/HtmlForm.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -262,7 +261,7 @@ else if (HtmlButton.TAG_NAME.equals(element.getTagName())

private boolean areChildrenValid() {
boolean valid = true;
for (final HtmlElement element : getFormHtmlElementDescendants()) {
for (final HtmlElement element : getElements()) {
if (element instanceof HtmlInput && !element.isValid()) {
if (LOG.isInfoEnabled()) {
LOG.info("Form validation failed; element '" + element + "' was not valid. Submit cancelled.");
Expand Down Expand Up @@ -459,7 +458,7 @@ public Page reset() {
Collection<SubmittableElement> getSubmittableElements(final SubmittableElement submitElement) {
final List<SubmittableElement> submittableElements = new ArrayList<>();

for (final HtmlElement element : getFormHtmlElementDescendants()) {
for (final HtmlElement element : getElements()) {
if (isSubmittable(element, submitElement)) {
submittableElements.add((SubmittableElement) element);
}
Expand Down Expand Up @@ -581,11 +580,11 @@ private <E extends HtmlElement> List<E> getFormElementsByAttribute(
final List<E> list = new ArrayList<>();
final String lowerCaseTagName = elementName.toLowerCase(Locale.ROOT);

for (final HtmlElement next : getFormHtmlElementDescendants()) {
if (next.getTagName().equals(lowerCaseTagName)) {
final String attValue = next.getAttribute(attributeName);
for (final HtmlElement element : getElements()) {
if (element.getTagName().equals(lowerCaseTagName)) {
final String attValue = element.getAttribute(attributeName);
if (attValue.equals(attributeValue)) {
list.add((E) next);
list.add((E) element);
}
}
}
Expand All @@ -595,29 +594,47 @@ private <E extends HtmlElement> List<E> getFormElementsByAttribute(
/**
* <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
*
* Same as {@link #getHtmlElementDescendants} but ignoring elements that are contained in a nested form.
* @return an {@link Iterable} that will recursively iterate over all of this node's {@link HtmlElement}
* descendants but ignoring elements that are contained in a nested form
* @return returns a list of all form controls contained in the <form> element or referenced by formId
* but ignoring elements that are contained in a nested form
*/
public Iterable<HtmlElement> getFormHtmlElementDescendants() {
final Iterator<HtmlElement> iter = new DescendantElementsIterator<HtmlElement>(HtmlElement.class) {
private boolean filterChildrenOfNestedForms_;

@Override
protected boolean isAccepted(final DomNode node) {
if (node instanceof HtmlForm) {
filterChildrenOfNestedForms_ = true;
return false;
public List<HtmlElement> getElements() {
final List<HtmlElement> elements = new ArrayList<>();

final String formId = getId();
final boolean formAttribSupported = formId != ATTRIBUTE_NOT_DEFINED
&& getPage().getWebClient().getBrowserVersion().hasFeature(FORM_SUBMISSION_FORM_ATTRIBUTE);

final HashSet<HtmlElement> nestedForms = new HashSet<>();

for (final HtmlElement element : ((HtmlPage) getPage()).getBody().getHtmlElementDescendants()) {
if (element != this && element instanceof HtmlForm && this.isAncestorOf(element)) {
nestedForms.add(element);
continue;
}

if (SUBMITTABLE_ELEMENT_NAMES.contains(element.getTagName())) {
if (isAncestorOf(element)) {
elements.add(element);
continue;
}

final boolean accepted = super.isAccepted(node);
if (accepted && filterChildrenOfNestedForms_) {
return ((HtmlElement) node).getEnclosingForm() == HtmlForm.this;
if (formAttribSupported) {
final String formIdRef = element.getAttribute("form");
if (formId.equals(formIdRef)) {
elements.add(element);
continue;
}
}
return accepted;
}
};
return () -> iter;
}

for (final HtmlElement element : lostChildren_) {
if (SUBMITTABLE_ELEMENT_NAMES.contains(element.getTagName())) {
elements.add(element);
}
}

return elements;
}

/**
Expand Down Expand Up @@ -986,10 +1003,10 @@ public <I extends HtmlInput> I getInputByValue(final String value) throws Elemen
public List<HtmlInput> getInputsByValue(final String value) {
final List<HtmlInput> results = new ArrayList<>();

for (final HtmlElement next : getFormHtmlElementDescendants()) {
if (next instanceof HtmlInput
&& Objects.equals(((HtmlInput) next).getValue(), value)) {
results.add((HtmlInput) next);
for (final HtmlElement element : getElements()) {
if (element instanceof HtmlInput
&& Objects.equals(((HtmlInput) element).getValue(), value)) {
results.add((HtmlInput) element);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,12 @@
import org.htmlunit.html.DomNode;
import org.htmlunit.html.FormFieldWithNameHistory;
import org.htmlunit.html.HtmlAttributeChangeEvent;
import org.htmlunit.html.HtmlButton;
import org.htmlunit.html.HtmlElement;
import org.htmlunit.html.HtmlForm;
import org.htmlunit.html.HtmlImage;
import org.htmlunit.html.HtmlImageInput;
import org.htmlunit.html.HtmlInput;
import org.htmlunit.html.HtmlPage;
import org.htmlunit.html.HtmlSelect;
import org.htmlunit.html.HtmlTextArea;
import org.htmlunit.html.SubmittableElement;
import org.htmlunit.javascript.configuration.JsxClass;
import org.htmlunit.javascript.configuration.JsxConstructor;
Expand Down Expand Up @@ -133,15 +130,9 @@ protected Object getWithPreemption(final String name) {
final List<DomNode> response = new ArrayList<>();
final DomNode domNode = getDomNodeOrNull();
if (domNode == null) {
return response;
return new ArrayList<>();
}
for (final HtmlElement desc : ((HtmlForm) domNode).getFormHtmlElementDescendants()) {
if (desc instanceof HtmlInput || desc instanceof HtmlButton
|| desc instanceof HtmlTextArea || desc instanceof HtmlSelect) {
response.add(desc);
}
}

response.addAll(((HtmlForm) domNode).getElements());
response.addAll(htmlForm.getLostChildren());
return response;
});
Expand Down
5 changes: 4 additions & 1 deletion src/test/java/org/htmlunit/html/HtmlForm2Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ public void jSSubmit_JavaScriptAction() throws Exception {
* @throws Exception if the test page can't be loaded
*/
@Test
@Alerts({"1", "val2"})
@Alerts({"1", "val2", "3", "3"})
public void malformedHtml_nestedForms() throws Exception {
final String html
= "<html><head>\n"
Expand All @@ -279,6 +279,9 @@ public void malformedHtml_nestedForms() throws Exception {
+ " function test() {\n"
+ " log(document.forms.length);\n"
+ " log(document.forms[0].field2.value);\n"

+ " log(document.forms[0].length);\n"
+ " log(document.forms[0].elements.length);\n"
+ " }\n"
+ "</script></head><body onload='test()'>\n"
+ "<form id='form1' method='get' action='foo'>\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,40 @@ public void elementsAccessorOutOfBound() throws Exception {
loadPageVerifyTitle2(html);
}

/**
* @throws Exception if the test fails
*/
@Test
@Alerts(DEFAULT = {"3", "textInput1", "button1", "textInput3"},
IE = {"1", "button1"})
public void elementsAccessorFormAttribute() throws Exception {
final String html
= "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ "function doTest() {\n"
+ " log(document.form1.length);\n"
+ " for (var i = 0; i < document.form1.length; i++) {\n"
+ " var element = document.form1.elements[i];\n"
+ " log(element.name);\n"
+ " }\n"
+ "}\n"
+ "</script></head>\n"
+ "<body onload='doTest()'>\n"

+ "<input type='text' name='textInput1' form='myForm'/>\n"
+ "<input type='text' name='textInput2' form='form1'/>\n"

+ "<form id='myForm' name='form1'>\n"
+ " <input type='button' name='button1' />\n"
+ "</form>\n"

+ "<input type='text' name='textInput3' form='myForm'/>\n"
+ "<input type='text' name='textInput4' form='form1'/>\n"
+ "</body></html>";

loadPageVerifyTitle2(html);
}

/**
* @throws Exception if the test fails
*/
Expand Down

0 comments on commit 00ce1ab

Please sign in to comment.