Skip to content

Commit

Permalink
Accessing form elements by id (e.g. myForm.elementId) searches only f…
Browse files Browse the repository at this point in the history
…orm elements and not arbitrary descendants
  • Loading branch information
rbri committed Oct 3, 2023
1 parent 01a643b commit c997b87
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 31 deletions.
4 changes: 4 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
<action type="update" dev="rbri">
Upgrade Apache commons-io to 2.14.0.
</action>
<action type="fix" dev="rbri">
Accessing form elements by id (e.g. myForm.elementId) searches only form elements and not
arbitrary descendants.
</action>
<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.
Expand Down
12 changes: 2 additions & 10 deletions src/main/java/org/htmlunit/html/HtmlForm.java
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ 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>
*
* @return returns a list of all form controls contained in the <form> element or referenced by formId
* @return returns a list of all form controls contained in the &lt;form&gt; element or referenced by formId
* but ignoring elements that are contained in a nested form
*/
public List<HtmlElement> getElements() {
Expand All @@ -577,14 +577,6 @@ public List<HtmlElement> getElements() {
}
}

for (final HtmlElement element : lostChildren_) {
if (SUBMITTABLE_ELEMENT_NAMES.contains(element.getTagName())
&& element.getEnclosingForm() == this
&& !elements.contains(element)) {
elements.add(element);
}
}

return elements;
}

Expand Down Expand Up @@ -711,7 +703,7 @@ public List<HtmlRadioButtonInput> getRadioButtonsByName(final String name) {
* @param radioButtonInput the radio button to select
*/
void setCheckedRadioButton(final HtmlRadioButtonInput radioButtonInput) {
if (radioButtonInput.getEnclosingForm() == null && !lostChildren_.contains(radioButtonInput)) {
if (radioButtonInput.getEnclosingForm() == null) {
throw new IllegalArgumentException("HtmlRadioButtonInput is not child of this HtmlForm");
}
final List<HtmlRadioButtonInput> radios = getRadioButtonsByName(radioButtonInput.getNameAttribute());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,11 @@ List<HtmlElement> findElements(final String name) {
return elements;
}

addElements(name, form.getHtmlElementDescendants(), elements);
addElements(name, form.getLostChildren(), elements);
for (final HtmlElement element : form.getElements()) {
if (isAccessibleByIdOrName(element, name)) {
elements.add(element);
}
}

// If no form fields are found, browsers are able to find img elements by ID or name.
if (elements.isEmpty()) {
Expand All @@ -505,28 +508,13 @@ List<HtmlElement> findElements(final String name) {
return elements;
}

private void addElements(final String name, final Iterable<HtmlElement> nodes,
final List<HtmlElement> addTo) {
for (final HtmlElement node : nodes) {
if (isAccessibleByIdOrName(node, name)) {
addTo.add(node);
}
}
}

private HtmlElement findFirstElement(final String name) {
final HtmlForm form = (HtmlForm) getDomNodeOrNull();
if (form == null) {
return null;
}

for (final HtmlElement node : form.getHtmlElementDescendants()) {
if (isAccessibleByIdOrName(node, name)) {
return node;
}
}

for (final HtmlElement node : form.getLostChildren()) {
for (final HtmlElement node : form.getElements()) {
if (isAccessibleByIdOrName(node, name)) {
return node;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -943,14 +943,15 @@ private void fieldNamedSubmit(final String htmlSnippet, final String expected) t
* @throws Exception if the test fails
*/
@Test
@Alerts({"before", "2"})
public void fieldFoundWithID() throws Exception {
@Alerts({"before", "2", "undefined"})
public void fieldFoundWithId() throws Exception {
final String html = "<html><head>\n"
+ "<script>\n"
+ LOG_TITLE_FUNCTION
+ "function test() {\n"
+ " log(IRForm.IRText.value);\n"
+ " log(IRForm.myField.length);\n"
+ " log(IRForm.myDiv);\n"
+ "}\n"
+ "</script>\n"
+ "</head>\n"
Expand All @@ -961,11 +962,37 @@ public void fieldFoundWithID() throws Exception {
+ " <input type='image' id='myField' src='foo.gif'/>\n"
+ " <input type='text' name='myField'/>\n"
+ " <input type='text' id='myField'/>\n"
+ " <div id='myDiv'>oooo</div>\n"
+ " </form>\n"
+ "</body>\n"
+ "</html>";

getMockWebConnection().setDefaultResponse("Error: not found", 404, "Not Found", MimeType.TEXT_HTML);
loadPageVerifyTitle2(html);
}

/**
* @throws Exception if the test fails
*/
@Test
@Alerts(DEFAULT = {"[object HTMLInputElement]", "[object HTMLInputElement]"},
IE = {"undefined", "undefined"})
public void fieldFoundWithIdByReference() throws Exception {
final String html = "<html><head>\n"
+ "<script>\n"
+ LOG_TITLE_FUNCTION
+ "function test() {\n"
+ " log(IRForm.IRText);\n"
+ " log(IRForm.myField);\n"
+ "}\n"
+ "</script>\n"
+ "</head>\n"
+ "<body onload='test()'>\n"
+ " <form name='IRForm' id='testForm' action='#'>\n"
+ " </form>\n"
+ " <input type='text' id='IRText' value='abc' form='testForm' />\n"
+ " <input type='text' id='myField' value='xy' form='testForm'/>\n"
+ "</body>\n"
+ "</html>";

loadPageVerifyTitle2(html);
}
Expand Down

0 comments on commit c997b87

Please sign in to comment.