1 /*
2 * Copyright (c) 2002-2025 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.html;
16
17 import static org.htmlunit.BrowserVersionFeatures.FORM_IGNORE_REL_NOREFERRER;
18 import static org.htmlunit.BrowserVersionFeatures.FORM_SUBMISSION_HEADER_CACHE_CONTROL_MAX_AGE;
19
20 import java.net.MalformedURLException;
21 import java.net.URL;
22 import java.nio.charset.Charset;
23 import java.nio.charset.StandardCharsets;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.Objects;
32 import java.util.function.Predicate;
33 import java.util.regex.Pattern;
34
35 import org.apache.commons.lang3.ArrayUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.htmlunit.BrowserVersion;
40 import org.htmlunit.ElementNotFoundException;
41 import org.htmlunit.FormEncodingType;
42 import org.htmlunit.HttpHeader;
43 import org.htmlunit.HttpMethod;
44 import org.htmlunit.Page;
45 import org.htmlunit.ScriptResult;
46 import org.htmlunit.SgmlPage;
47 import org.htmlunit.WebAssert;
48 import org.htmlunit.WebClient;
49 import org.htmlunit.WebRequest;
50 import org.htmlunit.WebWindow;
51 import org.htmlunit.http.HttpUtils;
52 import org.htmlunit.javascript.host.event.Event;
53 import org.htmlunit.javascript.host.event.SubmitEvent;
54 import org.htmlunit.protocol.javascript.JavaScriptURLConnection;
55 import org.htmlunit.util.EncodingSniffer;
56 import org.htmlunit.util.NameValuePair;
57 import org.htmlunit.util.UrlUtils;
58
59 /**
60 * Wrapper for the HTML element "form".
61 *
62 * @author Mike Bowler
63 * @author David K. Taylor
64 * @author Brad Clarke
65 * @author Christian Sell
66 * @author Marc Guillemot
67 * @author George Murnock
68 * @author Kent Tong
69 * @author Ahmed Ashour
70 * @author Philip Graf
71 * @author Ronald Brill
72 * @author Frank Danek
73 * @author Anton Demydenko
74 * @author Lai Quang Duong
75 */
76 public class HtmlForm extends HtmlElement {
77 private static final Log LOG = LogFactory.getLog(HtmlForm.class);
78
79 /** The HTML tag represented by this element. */
80 public static final String TAG_NAME = "form";
81
82 /** The "novalidate" attribute name. */
83 private static final String ATTRIBUTE_NOVALIDATE = "novalidate";
84
85 /** The "formnovalidate" attribute name. */
86 public static final String ATTRIBUTE_FORMNOVALIDATE = "formnovalidate";
87
88 private static final HashSet<String> SUBMITTABLE_TAG_NAMES = new HashSet<>(Arrays.asList(HtmlInput.TAG_NAME,
89 HtmlButton.TAG_NAME, HtmlSelect.TAG_NAME, HtmlTextArea.TAG_NAME));
90
91 private static final Pattern SUBMIT_CHARSET_PATTERN = Pattern.compile("[ ,].*");
92
93 private boolean isPreventDefault_;
94
95 /**
96 * Creates an instance.
97 *
98 * @param qualifiedName the qualified name of the element type to instantiate
99 * @param htmlPage the page that contains this element
100 * @param attributes the initial attributes
101 */
102 HtmlForm(final String qualifiedName, final SgmlPage htmlPage,
103 final Map<String, DomAttr> attributes) {
104 super(qualifiedName, htmlPage, attributes);
105 }
106
107 /**
108 * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
109 *
110 * <p>Submits this form to the server. If <code>submitElement</code> is {@code null}, then
111 * the submission is treated as if it was triggered by JavaScript, and the <code>onsubmit</code>
112 * handler will not be executed.</p>
113 *
114 * <p><b>IMPORTANT:</b> Using this method directly is not the preferred way of submitting forms.
115 * Most consumers should emulate the user's actions instead, probably by using something like
116 * {@link HtmlElement#click()} or {@link HtmlElement#dblClick()}.</p>
117 *
118 * @param submitElement the element that caused the submit to occur
119 */
120 public void submit(final SubmittableElement submitElement) {
121 final HtmlPage htmlPage = (HtmlPage) getPage();
122 final WebClient webClient = htmlPage.getWebClient();
123
124 if (webClient.isJavaScriptEnabled()) {
125 if (submitElement != null) {
126 isPreventDefault_ = false;
127
128 boolean validate = true;
129 if (submitElement instanceof HtmlSubmitInput
130 && ((HtmlSubmitInput) submitElement).isFormNoValidate()) {
131 validate = false;
132 }
133 else if (submitElement instanceof HtmlButton) {
134 final HtmlButton htmlButton = (HtmlButton) submitElement;
135 if ("submit".equalsIgnoreCase(htmlButton.getType())
136 && htmlButton.isFormNoValidate()) {
137 validate = false;
138 }
139 }
140
141 if (validate
142 && getAttributeDirect(ATTRIBUTE_NOVALIDATE) != ATTRIBUTE_NOT_DEFINED) {
143 validate = false;
144 }
145
146 if (validate && !areChildrenValid()) {
147 return;
148 }
149 final ScriptResult scriptResult = fireEvent(new SubmitEvent(this,
150 ((HtmlElement) submitElement).getScriptableObject()));
151 if (isPreventDefault_) {
152 // null means 'nothing executed'
153 if (scriptResult == null) {
154 return;
155 }
156 return;
157 }
158 }
159
160 final String action = getActionAttribute().trim();
161 if (org.htmlunit.util.StringUtils.startsWithIgnoreCase(action,
162 JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
163 htmlPage.executeJavaScript(action, "Form action", getStartLineNumber());
164 return;
165 }
166 }
167 else {
168 if (org.htmlunit.util.StringUtils.startsWithIgnoreCase(getActionAttribute(),
169 JavaScriptURLConnection.JAVASCRIPT_PREFIX)) {
170 // The action is JavaScript but JavaScript isn't enabled.
171 return;
172 }
173 }
174
175 // html5 attribute's support
176 if (submitElement != null) {
177 updateHtml5Attributes(submitElement);
178 }
179
180 // dialog support
181 final String methodAttribute = getMethodAttribute();
182 if ("dialog".equalsIgnoreCase(methodAttribute)) {
183 // find parent dialog
184 final HtmlElement dialog = getEnclosingElement("dialog");
185 if (dialog != null) {
186 ((HtmlDialog) dialog).close("");
187 }
188 return;
189 }
190
191 final WebRequest request = getWebRequest(submitElement);
192 final String target = htmlPage.getResolvedTarget(getTargetAttribute());
193
194 final WebWindow webWindow = htmlPage.getEnclosingWindow();
195 // Calling form.submit() twice forces double download.
196 webClient.download(webWindow, target, request, false, null, "JS form.submit()");
197 }
198
199 /**
200 * Check if element which cause submit contains new html5 attributes
201 * (formaction, formmethod, formtarget, formenctype)
202 * and override existing values
203 * @param submitElement the element to update
204 */
205 private void updateHtml5Attributes(final SubmittableElement submitElement) {
206 if (submitElement instanceof HtmlElement) {
207 final HtmlElement element = (HtmlElement) submitElement;
208
209 final String type = element.getAttributeDirect(TYPE_ATTRIBUTE);
210 boolean typeImage = false;
211 final boolean isInput = HtmlInput.TAG_NAME.equals(element.getTagName());
212 if (isInput) {
213 typeImage = "image".equalsIgnoreCase(type);
214 }
215
216 // could be excessive validation but support of html5 fromxxx
217 // attributes available for:
218 // - input with 'submit' and 'image' types
219 // - button with 'submit' or without type
220 final boolean typeSubmit = "submit".equalsIgnoreCase(type);
221 if (isInput && !typeSubmit && !typeImage) {
222 return;
223 }
224 else if (HtmlButton.TAG_NAME.equals(element.getTagName())
225 && !"submit".equals(((HtmlButton) element).getType())) {
226 return;
227 }
228
229 final String formaction = element.getAttributeDirect("formaction");
230 if (ATTRIBUTE_NOT_DEFINED != formaction) {
231 setActionAttribute(formaction);
232 }
233 final String formmethod = element.getAttributeDirect("formmethod");
234 if (ATTRIBUTE_NOT_DEFINED != formmethod) {
235 setMethodAttribute(formmethod);
236 }
237 final String formtarget = element.getAttributeDirect("formtarget");
238 if (ATTRIBUTE_NOT_DEFINED != formtarget) {
239 setTargetAttribute(formtarget);
240 }
241 final String formenctype = element.getAttributeDirect("formenctype");
242 if (ATTRIBUTE_NOT_DEFINED != formenctype) {
243 setEnctypeAttribute(formenctype);
244 }
245 }
246 }
247
248 private boolean areChildrenValid() {
249 boolean valid = true;
250 for (final HtmlElement element : getElements(htmlElement -> htmlElement instanceof HtmlInput)) {
251 if (!element.isValid()) {
252 if (LOG.isInfoEnabled()) {
253 LOG.info("Form validation failed; element '" + element + "' was not valid. Submit cancelled.");
254 }
255 valid = false;
256 break;
257 }
258 }
259 return valid;
260 }
261
262 /**
263 * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
264 *
265 * Gets the request for a submission of this form with the specified SubmittableElement.
266 * @param submitElement the element that caused the submit to occur
267 * @return the request
268 */
269 public WebRequest getWebRequest(final SubmittableElement submitElement) {
270 final HttpMethod method;
271 final String methodAttribute = getMethodAttribute();
272 if ("post".equalsIgnoreCase(methodAttribute)) {
273 method = HttpMethod.POST;
274 }
275 else {
276 if (!"get".equalsIgnoreCase(methodAttribute)
277 && org.htmlunit.util.StringUtils.isNotBlank(methodAttribute)) {
278 notifyIncorrectness("Incorrect submit method >" + getMethodAttribute() + "<. Using >GET<.");
279 }
280 method = HttpMethod.GET;
281 }
282
283 String actionUrl = getActionAttribute();
284 String anchor = null;
285 String queryFormFields = "";
286 Charset enc = getSubmitCharset();
287 if (StandardCharsets.UTF_16 == enc
288 || StandardCharsets.UTF_16BE == enc
289 || StandardCharsets.UTF_16LE == enc) {
290 enc = StandardCharsets.UTF_8;
291 }
292
293 final List<NameValuePair> parameters = getParameterListForSubmit(submitElement);
294 if (HttpMethod.GET == method) {
295 if (actionUrl.contains("#")) {
296 anchor = StringUtils.substringAfter(actionUrl, "#");
297 }
298 queryFormFields = HttpUtils.toQueryFormFields(parameters, enc);
299
300 // action may already contain some query parameters: they have to be removed
301 actionUrl = org.htmlunit.util.StringUtils.substringBefore(actionUrl, "#");
302 actionUrl = org.htmlunit.util.StringUtils.substringBefore(actionUrl, "?");
303 parameters.clear(); // parameters have been added to query
304 }
305
306 final HtmlPage htmlPage = (HtmlPage) getPage();
307 URL url;
308 try {
309 if (actionUrl.isEmpty()) {
310 url = WebClient.expandUrl(htmlPage.getUrl(), actionUrl);
311 }
312 else {
313 url = htmlPage.getFullyQualifiedUrl(actionUrl);
314 }
315
316 if (!queryFormFields.isEmpty()) {
317 url = UrlUtils.getUrlWithNewQuery(url, queryFormFields);
318 }
319
320 if (anchor != null && UrlUtils.URL_ABOUT_BLANK != url) {
321 url = UrlUtils.getUrlWithNewRef(url, anchor);
322 }
323 }
324 catch (final MalformedURLException e) {
325 throw new IllegalArgumentException("Not a valid url: " + actionUrl, e);
326 }
327
328 final BrowserVersion browser = htmlPage.getWebClient().getBrowserVersion();
329 final WebRequest request = new WebRequest(url, browser.getHtmlAcceptHeader(),
330 browser.getAcceptEncodingHeader());
331 request.setHttpMethod(method);
332 request.setRequestParameters(parameters);
333 if (HttpMethod.POST == method) {
334 request.setEncodingType(FormEncodingType.getInstance(getEnctypeAttribute()));
335 }
336 request.setCharset(enc);
337
338 // forms are ignoring the rel='noreferrer'
339 if (browser.hasFeature(FORM_IGNORE_REL_NOREFERRER)
340 || !relContainsNoreferrer()) {
341 request.setRefererHeader(htmlPage.getUrl());
342 }
343
344 if (HttpMethod.POST == method) {
345 try {
346 request.setAdditionalHeader(HttpHeader.ORIGIN,
347 UrlUtils.getUrlWithProtocolAndAuthority(htmlPage.getUrl()).toExternalForm());
348 }
349 catch (final MalformedURLException e) {
350 if (LOG.isInfoEnabled()) {
351 LOG.info("Invalid origin url '" + htmlPage.getUrl() + "'");
352 }
353 }
354 }
355 if (HttpMethod.POST == method) {
356 if (browser.hasFeature(FORM_SUBMISSION_HEADER_CACHE_CONTROL_MAX_AGE)) {
357 request.setAdditionalHeader(HttpHeader.CACHE_CONTROL, "max-age=0");
358 }
359 }
360
361 return request;
362 }
363
364 private boolean relContainsNoreferrer() {
365 String rel = getRelAttribute();
366 if (rel != null) {
367 rel = rel.toLowerCase(Locale.ROOT);
368 return ArrayUtils.contains(org.htmlunit.util.StringUtils.splitAtBlank(rel), "noreferrer");
369 }
370 return false;
371 }
372
373 /**
374 * Returns the charset to use for the form submission. This is the first one
375 * from the list provided in {@link #getAcceptCharsetAttribute()} if any
376 * or the page's charset else
377 * @return the charset to use for the form submission
378 */
379 private Charset getSubmitCharset() {
380 String charset = getAcceptCharsetAttribute();
381 if (!charset.isEmpty()) {
382 charset = charset.trim();
383 return EncodingSniffer.toCharset(
384 SUBMIT_CHARSET_PATTERN.matcher(charset).replaceAll("").toUpperCase(Locale.ROOT));
385 }
386 return getPage().getCharset();
387 }
388
389 /**
390 * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
391 *
392 * Returns a list of {@link NameValuePair}s that represent the data that will be
393 * sent to the server when this form is submitted. This is primarily intended to aid
394 * debugging.
395 *
396 * @param submitElement the element used to submit the form, or {@code null} if the
397 * form was submitted by JavaScript
398 * @return the list of {@link NameValuePair}s that represent that data that will be sent
399 * to the server when this form is submitted
400 */
401 public List<NameValuePair> getParameterListForSubmit(final SubmittableElement submitElement) {
402 final Collection<SubmittableElement> submittableElements = getSubmittableElements(submitElement);
403
404 final List<NameValuePair> parameterList = new ArrayList<>(submittableElements.size());
405 for (final SubmittableElement element : submittableElements) {
406 parameterList.addAll(Arrays.asList(element.getSubmitNameValuePairs()));
407 }
408
409 return parameterList;
410 }
411
412 /**
413 * Resets this form to its initial values, returning the page contained by this form's window after the
414 * reset. Note that the returned page may or may not be the same as the original page, based on JavaScript
415 * event handlers, etc.
416 *
417 * @return the page contained by this form's window after the reset
418 */
419 public Page reset() {
420 final SgmlPage sgmlPage = getPage();
421 final ScriptResult scriptResult = fireEvent(Event.TYPE_RESET);
422 if (ScriptResult.isFalse(scriptResult)) {
423 return sgmlPage.getWebClient().getCurrentWindow().getEnclosedPage();
424 }
425
426 for (final HtmlElement next : getHtmlElementDescendants()) {
427 if (next instanceof SubmittableElement) {
428 ((SubmittableElement) next).reset();
429 }
430 }
431
432 return sgmlPage;
433 }
434
435 /**
436 * {@inheritDoc}
437 */
438 @Override
439 public boolean isValid() {
440 for (final HtmlElement element : getFormElements()) {
441 if (!element.isValid()) {
442 return false;
443 }
444 }
445 return super.isValid();
446 }
447
448 /**
449 * Returns a collection of elements that represent all the "submittable" elements in this form,
450 * assuming that the specified element is used to submit the form.
451 *
452 * @param submitElement the element used to submit the form, or {@code null} if the
453 * form is submitted by JavaScript
454 * @return a collection of elements that represent all the "submittable" elements in this form
455 */
456 Collection<SubmittableElement> getSubmittableElements(final SubmittableElement submitElement) {
457 final List<SubmittableElement> submittableElements = new ArrayList<>();
458
459 for (final HtmlElement element : getElements(htmlElement -> isSubmittable(htmlElement, submitElement))) {
460 submittableElements.add((SubmittableElement) element);
461 }
462
463 return submittableElements;
464 }
465
466 private static boolean isValidForSubmission(final HtmlElement element, final SubmittableElement submitElement) {
467 final String tagName = element.getTagName();
468 if (!SUBMITTABLE_TAG_NAMES.contains(tagName)) {
469 return false;
470 }
471 if (element.isDisabledElementAndDisabled()) {
472 return false;
473 }
474 // clicked input type="image" is submitted even if it hasn't a name
475 if (element == submitElement && element instanceof HtmlImageInput) {
476 return true;
477 }
478
479 if (!element.hasAttribute(NAME_ATTRIBUTE)) {
480 return false;
481 }
482
483 if (org.htmlunit.util.StringUtils.isEmptyString(element.getAttributeDirect(NAME_ATTRIBUTE))) {
484 return false;
485 }
486
487 if (element instanceof HtmlInput) {
488 final HtmlInput input = (HtmlInput) element;
489 if (input.isCheckable()) {
490 return ((HtmlInput) element).isChecked();
491 }
492 }
493 if (element instanceof HtmlSelect) {
494 return ((HtmlSelect) element).isValidForSubmission();
495 }
496 return true;
497 }
498
499 /**
500 * Returns {@code true} if the specified element gets submitted when this form is submitted,
501 * assuming that the form is submitted using the specified submit element.
502 *
503 * @param element the element to check
504 * @param submitElement the element used to submit the form, or {@code null} if the form is
505 * submitted by JavaScript
506 * @return {@code true} if the specified element gets submitted when this form is submitted
507 */
508 private static boolean isSubmittable(final HtmlElement element, final SubmittableElement submitElement) {
509 if (!isValidForSubmission(element, submitElement)) {
510 return false;
511 }
512
513 // The one submit button that was clicked can be submitted but no other ones
514 if (element == submitElement) {
515 return true;
516 }
517 if (element instanceof HtmlInput) {
518 final HtmlInput input = (HtmlInput) element;
519 if (!input.isSubmitable()) {
520 return false;
521 }
522 }
523
524 return !HtmlButton.TAG_NAME.equals(element.getTagName());
525 }
526
527 /**
528 * Returns all input elements which are members of this form and have the specified name.
529 *
530 * @param name the input name to search for
531 * @return all input elements which are members of this form and have the specified name
532 */
533 public List<HtmlInput> getInputsByName(final String name) {
534 return getFormElementsByAttribute(HtmlInput.TAG_NAME, NAME_ATTRIBUTE, name);
535 }
536
537 /**
538 * Same as {@link #getElementsByAttribute(String, String, String)} but
539 * ignoring elements that are contained in a nested form.
540 */
541 @SuppressWarnings("unchecked")
542 private <E extends HtmlElement> List<E> getFormElementsByAttribute(
543 final String elementName,
544 final String attributeName,
545 final String attributeValue) {
546
547 return (List<E>) getElements(htmlElement ->
548 htmlElement.getTagName().equals(elementName)
549 && htmlElement.getAttribute(attributeName).equals(attributeValue));
550 }
551
552 /**
553 * @return A List containing all form controls in the form.
554 * The form controls in the returned collection are in the same order
555 * in which they appear in the form by following a preorder,
556 * depth-first traversal of the tree. This is called tree order.
557 * Only the following elements are returned:
558 * button, fieldset, input, object, output, select, textarea.
559 */
560 public List<HtmlElement> getFormElements() {
561 return getElements(htmlElement -> {
562 final String tagName = htmlElement.getTagName();
563 return HtmlButton.TAG_NAME.equals(tagName)
564 || HtmlFieldSet.TAG_NAME.equals(tagName)
565 || HtmlInput.TAG_NAME.equals(tagName)
566 || HtmlObject.TAG_NAME.equals(tagName)
567 || HtmlOutput.TAG_NAME.equals(tagName)
568 || HtmlSelect.TAG_NAME.equals(tagName)
569 || HtmlTextArea.TAG_NAME.equals(tagName);
570 });
571 }
572
573 /**
574 * This is the backend for the getElements() javascript function of the form.
575 * see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/elements
576 *
577 * @return A List containing all non-image controls in the form.
578 * The form controls in the returned collection are in the same order
579 * in which they appear in the form by following a preorder,
580 * depth-first traversal of the tree. This is called tree order.
581 * Only the following elements are returned:
582 * button, fieldset,
583 * input (with the exception that any whose type is "image" are omitted for historical reasons),
584 * object, output, select, textarea.
585 */
586 public List<HtmlElement> getElementsJS() {
587 return getElements(htmlElement -> {
588 final String tagName = htmlElement.getTagName();
589 if (HtmlInput.TAG_NAME.equals(tagName)) {
590 return !(htmlElement instanceof HtmlImageInput);
591 }
592
593 return HtmlButton.TAG_NAME.equals(tagName)
594 || HtmlFieldSet.TAG_NAME.equals(tagName)
595 || HtmlObject.TAG_NAME.equals(tagName)
596 || HtmlOutput.TAG_NAME.equals(tagName)
597 || HtmlSelect.TAG_NAME.equals(tagName)
598 || HtmlTextArea.TAG_NAME.equals(tagName);
599 });
600 }
601
602 /**
603 * @param filter a predicate to filter the element
604 * @return all elements attached to this form and matching the filter predicate
605 */
606 public List<HtmlElement> getElements(final Predicate<HtmlElement> filter) {
607 final List<HtmlElement> elements = new ArrayList<>();
608
609 if (isAttachedToPage()) {
610 for (final HtmlElement element : getPage().getDocumentElement().getHtmlElementDescendants()) {
611 if (filter.test(element)
612 && element.getEnclosingForm() == this) {
613 elements.add(element);
614 }
615 }
616 }
617 else {
618 for (final HtmlElement element : getHtmlElementDescendants()) {
619 if (filter.test(element)) {
620 elements.add(element);
621 }
622 }
623 }
624
625 return elements;
626 }
627
628 /**
629 * Returns the first input element which is a member of this form and has the specified name.
630 *
631 * @param name the input name to search for
632 * @param <I> the input type
633 * @return the first input element which is a member of this form and has the specified name
634 * @throws ElementNotFoundException if there is no input in this form with the specified name
635 */
636 @SuppressWarnings("unchecked")
637 public final <I extends HtmlInput> I getInputByName(final String name) throws ElementNotFoundException {
638 final List<HtmlInput> inputs = getInputsByName(name);
639
640 if (inputs.isEmpty()) {
641 throw new ElementNotFoundException(HtmlInput.TAG_NAME, NAME_ATTRIBUTE, name);
642 }
643 return (I) inputs.get(0);
644 }
645
646 /**
647 * Returns all the {@link HtmlSelect} elements in this form that have the specified name.
648 *
649 * @param name the name to search for
650 * @return all the {@link HtmlSelect} elements in this form that have the specified name
651 */
652 public List<HtmlSelect> getSelectsByName(final String name) {
653 return getFormElementsByAttribute(HtmlSelect.TAG_NAME, NAME_ATTRIBUTE, name);
654 }
655
656 /**
657 * Returns the first {@link HtmlSelect} element in this form that has the specified name.
658 *
659 * @param name the name to search for
660 * @return the first {@link HtmlSelect} element in this form that has the specified name
661 * @throws ElementNotFoundException if this form does not contain a {@link HtmlSelect}
662 * element with the specified name
663 */
664 public HtmlSelect getSelectByName(final String name) throws ElementNotFoundException {
665 final List<HtmlSelect> list = getSelectsByName(name);
666 if (list.isEmpty()) {
667 throw new ElementNotFoundException(HtmlSelect.TAG_NAME, NAME_ATTRIBUTE, name);
668 }
669 return list.get(0);
670 }
671
672 /**
673 * Returns all the {@link HtmlButton} elements in this form that have the specified name.
674 *
675 * @param name the name to search for
676 * @return all the {@link HtmlButton} elements in this form that have the specified name
677 */
678 public List<HtmlButton> getButtonsByName(final String name) {
679 return getFormElementsByAttribute(HtmlButton.TAG_NAME, NAME_ATTRIBUTE, name);
680 }
681
682 /**
683 * Returns the first {@link HtmlButton} element in this form that has the specified name.
684 *
685 * @param name the name to search for
686 * @return the first {@link HtmlButton} element in this form that has the specified name
687 * @throws ElementNotFoundException if this form does not contain a {@link HtmlButton}
688 * element with the specified name
689 */
690 public HtmlButton getButtonByName(final String name) throws ElementNotFoundException {
691 final List<HtmlButton> list = getButtonsByName(name);
692 if (list.isEmpty()) {
693 throw new ElementNotFoundException(HtmlButton.TAG_NAME, NAME_ATTRIBUTE, name);
694 }
695 return list.get(0);
696 }
697
698 /**
699 * Returns all the {@link HtmlTextArea} elements in this form that have the specified name.
700 *
701 * @param name the name to search for
702 * @return all the {@link HtmlTextArea} elements in this form that have the specified name
703 */
704 public List<HtmlTextArea> getTextAreasByName(final String name) {
705 return getFormElementsByAttribute(HtmlTextArea.TAG_NAME, NAME_ATTRIBUTE, name);
706 }
707
708 /**
709 * Returns the first {@link HtmlTextArea} element in this form that has the specified name.
710 *
711 * @param name the name to search for
712 * @return the first {@link HtmlTextArea} element in this form that has the specified name
713 * @throws ElementNotFoundException if this form does not contain a {@link HtmlTextArea}
714 * element with the specified name
715 */
716 public HtmlTextArea getTextAreaByName(final String name) throws ElementNotFoundException {
717 final List<HtmlTextArea> list = getTextAreasByName(name);
718 if (list.isEmpty()) {
719 throw new ElementNotFoundException(HtmlTextArea.TAG_NAME, NAME_ATTRIBUTE, name);
720 }
721 return list.get(0);
722 }
723
724 /**
725 * Returns all the {@link HtmlRadioButtonInput} elements in this form that have the specified name.
726 *
727 * @param name the name to search for
728 * @return all the {@link HtmlRadioButtonInput} elements in this form that have the specified name
729 */
730 public List<HtmlRadioButtonInput> getRadioButtonsByName(final String name) {
731 WebAssert.notNull("name", name);
732
733 final List<HtmlRadioButtonInput> results = new ArrayList<>();
734
735 for (final HtmlElement element : getInputsByName(name)) {
736 if (element instanceof HtmlRadioButtonInput) {
737 results.add((HtmlRadioButtonInput) element);
738 }
739 }
740
741 return results;
742 }
743
744 /**
745 * Selects the specified radio button in the form. Only a radio button that is actually contained
746 * in the form can be selected.
747 *
748 * @param radioButtonInput the radio button to select
749 */
750 void setCheckedRadioButton(final HtmlRadioButtonInput radioButtonInput) {
751 if (radioButtonInput.getEnclosingForm() == null) {
752 throw new IllegalArgumentException("HtmlRadioButtonInput is not child of this HtmlForm");
753 }
754 final List<HtmlRadioButtonInput> radios = getRadioButtonsByName(radioButtonInput.getNameAttribute());
755
756 for (final HtmlRadioButtonInput input : radios) {
757 input.setCheckedInternal(input == radioButtonInput);
758 }
759 }
760
761 /**
762 * Returns the first checked radio button with the specified name. If none of
763 * the radio buttons by that name are checked, this method returns {@code null}.
764 *
765 * @param name the name of the radio button
766 * @return the first checked radio button with the specified name
767 */
768 public HtmlRadioButtonInput getCheckedRadioButton(final String name) {
769 WebAssert.notNull("name", name);
770
771 for (final HtmlRadioButtonInput input : getRadioButtonsByName(name)) {
772 if (input.isChecked()) {
773 return input;
774 }
775 }
776 return null;
777 }
778
779 /**
780 * Returns the value of the attribute {@code action}. Refer to the <a
781 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
782 * details on the use of this attribute.
783 *
784 * @return the value of the attribute {@code action} or an empty string if that attribute isn't defined
785 */
786 public final String getActionAttribute() {
787 return getAttributeDirect("action");
788 }
789
790 /**
791 * Sets the value of the attribute {@code action}. Refer to the <a
792 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
793 * details on the use of this attribute.
794 *
795 * @param action the value of the attribute {@code action}
796 */
797 public final void setActionAttribute(final String action) {
798 setAttribute("action", action);
799 }
800
801 /**
802 * Returns the value of the attribute {@code method}. Refer to the <a
803 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
804 * details on the use of this attribute.
805 *
806 * @return the value of the attribute {@code method} or an empty string if that attribute isn't defined
807 */
808 public final String getMethodAttribute() {
809 return getAttributeDirect("method");
810 }
811
812 /**
813 * Sets the value of the attribute {@code method}. Refer to the <a
814 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
815 * details on the use of this attribute.
816 *
817 * @param method the value of the attribute {@code method}
818 */
819 public final void setMethodAttribute(final String method) {
820 setAttribute("method", method);
821 }
822
823 /**
824 * Returns the value of the attribute {@code name}. Refer to the <a
825 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
826 * details on the use of this attribute.
827 *
828 * @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
829 */
830 public final String getNameAttribute() {
831 return getAttributeDirect(NAME_ATTRIBUTE);
832 }
833
834 /**
835 * Sets the value of the attribute {@code name}. Refer to the <a
836 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
837 * details on the use of this attribute.
838 *
839 * @param name the value of the attribute {@code name}
840 */
841 public final void setNameAttribute(final String name) {
842 setAttribute(NAME_ATTRIBUTE, name);
843 }
844
845 /**
846 * Returns the value of the attribute {@code enctype}. Refer to the <a
847 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
848 * details on the use of this attribute. "Enctype" is the encoding type
849 * used when submitting a form back to the server.
850 *
851 * @return the value of the attribute {@code enctype} or an empty string if that attribute isn't defined
852 */
853 public final String getEnctypeAttribute() {
854 return getAttributeDirect("enctype");
855 }
856
857 /**
858 * Sets the value of the attribute {@code enctype}. Refer to the <a
859 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
860 * details on the use of this attribute. "Enctype" is the encoding type
861 * used when submitting a form back to the server.
862 *
863 * @param encoding the value of the attribute {@code enctype}
864 */
865 public final void setEnctypeAttribute(final String encoding) {
866 setAttribute("enctype", encoding);
867 }
868
869 /**
870 * Returns the value of the attribute {@code onsubmit}. Refer to the <a
871 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
872 * details on the use of this attribute.
873 *
874 * @return the value of the attribute {@code onsubmit} or an empty string if that attribute isn't defined
875 */
876 public final String getOnSubmitAttribute() {
877 return getAttributeDirect("onsubmit");
878 }
879
880 /**
881 * Returns the value of the attribute {@code onreset}. Refer to the <a
882 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
883 * details on the use of this attribute.
884 *
885 * @return the value of the attribute {@code onreset} or an empty string if that attribute isn't defined
886 */
887 public final String getOnResetAttribute() {
888 return getAttributeDirect("onreset");
889 }
890
891 /**
892 * Returns the value of the attribute {@code accept}. Refer to the <a
893 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
894 * details on the use of this attribute.
895 *
896 * @return the value of the attribute {@code accept} or an empty string if that attribute isn't defined
897 */
898 public final String getAcceptAttribute() {
899 return getAttribute(HttpHeader.ACCEPT_LC);
900 }
901
902 /**
903 * Returns the value of the attribute {@code accept-charset}. Refer to the <a
904 * href='http://www.w3.org/TR/html401/interact/forms.html#adef-accept-charset'>
905 * HTML 4.01</a> documentation for details on the use of this attribute.
906 *
907 * @return the value of the attribute {@code accept-charset} or an empty string if that attribute isn't defined
908 */
909 public final String getAcceptCharsetAttribute() {
910 return getAttribute("accept-charset");
911 }
912
913 /**
914 * Returns the value of the attribute {@code target}. Refer to the <a
915 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
916 * details on the use of this attribute.
917 *
918 * @return the value of the attribute {@code target} or an empty string if that attribute isn't defined
919 */
920 public final String getTargetAttribute() {
921 return getAttributeDirect("target");
922 }
923
924 /**
925 * Sets the value of the attribute {@code target}. Refer to the <a
926 * href='http://www.w3.org/TR/html401/'>HTML 4.01</a> documentation for
927 * details on the use of this attribute.
928 *
929 * @param target the value of the attribute {@code target}
930 */
931 public final void setTargetAttribute(final String target) {
932 setAttribute("target", target);
933 }
934
935 /**
936 * Returns the value of the attribute {@code rel}. Refer to the
937 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
938 * documentation for details on the use of this attribute.
939 *
940 * @return the value of the attribute {@code rel} or an empty string if that attribute isn't defined
941 */
942 public final String getRelAttribute() {
943 return getAttributeDirect("rel");
944 }
945
946 /**
947 * Returns the first input in this form with the specified value.
948 * @param value the value to search for
949 * @param <I> the input type
950 * @return the first input in this form with the specified value
951 * @throws ElementNotFoundException if this form does not contain any inputs with the specified value
952 */
953 @SuppressWarnings("unchecked")
954 public <I extends HtmlInput> I getInputByValue(final String value) throws ElementNotFoundException {
955 final List<HtmlInput> list = getInputsByValue(value);
956 if (list.isEmpty()) {
957 throw new ElementNotFoundException(HtmlInput.TAG_NAME, VALUE_ATTRIBUTE, value);
958 }
959 return (I) list.get(0);
960 }
961
962 /**
963 * Returns all the inputs in this form with the specified value.
964 * @param value the value to search for
965 * @return all the inputs in this form with the specified value
966 */
967 public List<HtmlInput> getInputsByValue(final String value) {
968 final List<HtmlInput> results = new ArrayList<>();
969
970 for (final HtmlElement element : getElements(htmlElement -> htmlElement instanceof HtmlInput)) {
971 if (Objects.equals(((HtmlInput) element).getValue(), value)) {
972 results.add((HtmlInput) element);
973 }
974 }
975
976 return results;
977 }
978
979 /**
980 * {@inheritDoc}
981 */
982 @Override
983 protected void preventDefault() {
984 isPreventDefault_ = true;
985 }
986
987 /**
988 * Browsers have problems with self closing form tags.
989 */
990 @Override
991 protected boolean isEmptyXmlTagExpanded() {
992 return true;
993 }
994
995 /**
996 * @return the value of the attribute {@code novalidate} or an empty string if that attribute isn't defined
997 */
998 public final boolean isNoValidate() {
999 return hasAttribute(ATTRIBUTE_NOVALIDATE);
1000 }
1001
1002 /**
1003 * Sets the value of the attribute {@code novalidate}.
1004 *
1005 * @param noValidate the value of the attribute {@code novalidate}
1006 */
1007 public final void setNoValidate(final boolean noValidate) {
1008 if (noValidate) {
1009 setAttribute(ATTRIBUTE_NOVALIDATE, ATTRIBUTE_NOVALIDATE);
1010 }
1011 else {
1012 removeAttribute(ATTRIBUTE_NOVALIDATE);
1013 }
1014 }
1015 }