1 /*
2 * Copyright (c) 2002-2026 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.HTMLINPUT_TYPE_IMAGE_IGNORES_CUSTOM_VALIDITY;
18 import static org.htmlunit.BrowserVersionFeatures.HTMLINPUT_TYPE_MONTH_SUPPORTED;
19 import static org.htmlunit.BrowserVersionFeatures.HTMLINPUT_TYPE_WEEK_SUPPORTED;
20 import static org.htmlunit.html.HtmlForm.ATTRIBUTE_FORMNOVALIDATE;
21
22 import java.net.MalformedURLException;
23 import java.util.Locale;
24 import java.util.Map;
25
26 import org.apache.commons.logging.Log;
27 import org.apache.commons.logging.LogFactory;
28 import org.htmlunit.BrowserVersion;
29 import org.htmlunit.HttpHeader;
30 import org.htmlunit.Page;
31 import org.htmlunit.ScriptResult;
32 import org.htmlunit.SgmlPage;
33 import org.htmlunit.WebClient;
34 import org.htmlunit.corejs.javascript.Context;
35 import org.htmlunit.corejs.javascript.regexp.RegExpEngineAccess;
36 import org.htmlunit.javascript.AbstractJavaScriptEngine;
37 import org.htmlunit.javascript.HtmlUnitContextFactory;
38 import org.htmlunit.javascript.host.event.Event;
39 import org.htmlunit.javascript.host.event.MouseEvent;
40 import org.htmlunit.javascript.host.html.HTMLInputElement;
41 import org.htmlunit.util.NameValuePair;
42 import org.htmlunit.util.StringUtils;
43 import org.xml.sax.helpers.AttributesImpl;
44
45 /**
46 * Wrapper for the HTML element "input".
47 *
48 * @author Mike Bowler
49 * @author David K. Taylor
50 * @author Christian Sell
51 * @author David D. Kilzer
52 * @author Marc Guillemot
53 * @author Daniel Gredler
54 * @author Ahmed Ashour
55 * @author Ronald Brill
56 * @author Frank Danek
57 * @author Anton Demydenko
58 * @author Ronny Shapiro
59 * @author Lai Quang Duong
60 */
61 public abstract class HtmlInput extends HtmlElement implements DisabledElement, SubmittableElement,
62 ValidatableElement {
63
64 private static final Log LOG = LogFactory.getLog(HtmlInput.class);
65
66 /** The HTML tag represented by this element. */
67 public static final String TAG_NAME = "input";
68
69 private String rawValue_;
70 private boolean isValueDirty_;
71 private boolean valueModifiedByJavascript_;
72 private Object valueAtFocus_;
73 private String customValidity_;
74
75 /**
76 * Creates an instance.
77 *
78 * @param page the page that contains this element
79 * @param attributes the initial attributes
80 */
81 public HtmlInput(final SgmlPage page, final Map<String, DomAttr> attributes) {
82 this(TAG_NAME, page, attributes);
83 }
84
85 /**
86 * Creates an instance.
87 *
88 * @param qualifiedName the qualified name of the element type to instantiate
89 * @param page the page that contains this element
90 * @param attributes the initial attributes
91 */
92 public HtmlInput(final String qualifiedName, final SgmlPage page,
93 final Map<String, DomAttr> attributes) {
94 super(qualifiedName, page, attributes);
95 rawValue_ = getValueAttribute();
96 }
97
98 /**
99 * Sets the content of the {@code value} attribute.
100 *
101 * @param newValue the new value
102 */
103 public void setValueAttribute(final String newValue) {
104 super.setAttribute(VALUE_ATTRIBUTE, newValue);
105 }
106
107 /**
108 * {@inheritDoc}
109 */
110 @Override
111 public NameValuePair[] getSubmitNameValuePairs() {
112 return new NameValuePair[]{new NameValuePair(getNameAttribute(), getValue())};
113 }
114
115 /**
116 * Returns the value of the attribute {@code type}. Refer to the
117 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
118 * documentation for details on the use of this attribute.
119 *
120 * @return the value of the attribute {@code type} or an empty string if that attribute isn't defined
121 */
122 public final String getTypeAttribute() {
123 final String type = getAttributeDirect(TYPE_ATTRIBUTE);
124 if (ATTRIBUTE_NOT_DEFINED == type) {
125 return "text";
126 }
127 return type;
128 }
129
130 /**
131 * Returns the value of the attribute {@code name}. Refer to the
132 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
133 * documentation for details on the use of this attribute.
134 *
135 * @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
136 */
137 public final String getNameAttribute() {
138 return getAttributeDirect(NAME_ATTRIBUTE);
139 }
140
141 /**
142 * <p>Return the value of the attribute "value". Refer to the
143 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
144 * documentation for details on the use of this attribute.</p>
145 *
146 * @return the value of the attribute {@code value} or an empty string if that attribute isn't defined
147 */
148 public final String getValueAttribute() {
149 return getAttributeDirect(VALUE_ATTRIBUTE);
150 }
151
152 /**
153 * Returns the value.
154 *
155 * @return the value
156 */
157 public String getValue() {
158 return getRawValue();
159 }
160
161 /**
162 * Sets the value.
163 *
164 * @param newValue the new value
165 */
166 public void setValue(final String newValue) {
167 setRawValue(newValue);
168 isValueDirty_ = true;
169 }
170
171 protected void valueAttributeChanged(final String attributeValue, final boolean isValueDirty) {
172 if (!isValueDirty) {
173 setRawValue(attributeValue);
174 }
175 }
176
177 /**
178 * Returns the value of the attribute {@code checked}. Refer to the
179 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
180 * documentation for details on the use of this attribute.
181 *
182 * @return the value of the attribute {@code checked} or an empty string if that attribute isn't defined
183 */
184 public final String getCheckedAttribute() {
185 return getAttributeDirect(ATTRIBUTE_CHECKED);
186 }
187
188 /**
189 * {@inheritDoc}
190 */
191 @Override
192 public final String getDisabledAttribute() {
193 return getAttributeDirect(ATTRIBUTE_DISABLED);
194 }
195
196 /**
197 * {@inheritDoc}
198 */
199 @Override
200 public final boolean isDisabled() {
201 if (hasAttribute(ATTRIBUTE_DISABLED)) {
202 return true;
203 }
204
205 DomNode node = getParentNode();
206 while (node != null) {
207 if (node instanceof DisabledElement element
208 && element.isDisabled()) {
209 return true;
210 }
211 node = node.getParentNode();
212 }
213
214 return false;
215 }
216
217 /**
218 * Returns the value of the attribute {@code readonly}. Refer to the
219 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
220 * documentation for details on the use of this attribute.
221 *
222 * @return the value of the attribute {@code readonly}
223 * or an empty string if that attribute isn't defined.
224 */
225 public final String getReadOnlyAttribute() {
226 return getAttributeDirect("readonly");
227 }
228
229 /**
230 * Returns the value of the attribute {@code size}. Refer to the
231 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
232 * documentation for details on the use of this attribute.
233 *
234 * @return the value of the attribute {@code size}
235 * or an empty string if that attribute isn't defined.
236 */
237 public final String getSizeAttribute() {
238 return getAttributeDirect("size");
239 }
240
241 /**
242 * Returns the value of the attribute {@code maxlength}. Refer to the
243 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
244 * documentation for details on the use of this attribute.
245 *
246 * @return the value of the attribute {@code maxlength}
247 * or an empty string if that attribute isn't defined.
248 */
249 public final String getMaxLengthAttribute() {
250 return getAttributeDirect("maxlength");
251 }
252
253 /**
254 * Gets the max length if defined, Integer.MAX_VALUE if none.
255 * @return the max length
256 */
257 protected int getMaxLength() {
258 final String maxLength = getMaxLengthAttribute();
259 if (maxLength.isEmpty()) {
260 return Integer.MAX_VALUE;
261 }
262
263 try {
264 return Integer.parseInt(maxLength.trim());
265 }
266 catch (final NumberFormatException e) {
267 return Integer.MAX_VALUE;
268 }
269 }
270
271 /**
272 * Returns the value of the attribute {@code minlength}. Refer to the
273 * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a>
274 * documentation for details on the use of this attribute.
275 *
276 * @return the value of the attribute {@code minlength}
277 * or an empty string if that attribute isn't defined.
278 */
279 public final String getMinLengthAttribute() {
280 return getAttributeDirect("minlength");
281 }
282
283 /**
284 * Gets the min length if defined, Integer.MIN_VALUE if none.
285 * @return the min length
286 */
287 protected int getMinLength() {
288 final String minLength = getMinLengthAttribute();
289 if (minLength.isEmpty()) {
290 return Integer.MIN_VALUE;
291 }
292
293 try {
294 return Integer.parseInt(minLength.trim());
295 }
296 catch (final NumberFormatException e) {
297 return Integer.MIN_VALUE;
298 }
299 }
300
301 /**
302 * Returns the value of the attribute {@code src}. Refer to the
303 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
304 * documentation for details on the use of this attribute.
305 *
306 * @return the value of the attribute {@code src}
307 * or an empty string if that attribute isn't defined.
308 */
309 public String getSrcAttribute() {
310 return getSrcAttributeNormalized();
311 }
312
313 /**
314 * Returns the value of the {@code src} value.
315 * @return the value of the {@code src} value
316 */
317 public String getSrc() {
318 final String src = getSrcAttributeNormalized();
319 if (ATTRIBUTE_NOT_DEFINED == src) {
320 return src;
321 }
322
323 final HtmlPage page = getHtmlPageOrNull();
324 if (page != null) {
325 try {
326 return page.getFullyQualifiedUrl(src).toExternalForm();
327 }
328 catch (final MalformedURLException e) {
329 // Log the error and fall through to the return values below.
330 if (LOG.isWarnEnabled()) {
331 LOG.warn(e.getMessage(), e);
332 }
333 }
334 }
335 return src;
336 }
337
338 /**
339 * Sets the {@code src} attribute.
340 *
341 * @param src the {@code src} attribute
342 */
343 public void setSrcAttribute(final String src) {
344 setAttribute(SRC_ATTRIBUTE, src);
345 }
346
347 /**
348 * Returns the value of the attribute {@code alt}. Refer to the
349 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
350 * documentation for details on the use of this attribute.
351 *
352 * @return the value of the attribute {@code alt}
353 * or an empty string if that attribute isn't defined.
354 */
355 public final String getAltAttribute() {
356 return getAttributeDirect("alt");
357 }
358
359 /**
360 * Returns the value of the attribute {@code usemap}. Refer to the
361 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
362 * documentation for details on the use of this attribute.
363 *
364 * @return the value of the attribute {@code usemap}
365 * or an empty string if that attribute isn't defined.
366 */
367 public final String getUseMapAttribute() {
368 return getAttributeDirect("usemap");
369 }
370
371 /**
372 * Returns the value of the attribute {@code tabindex}. Refer to the
373 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
374 * documentation for details on the use of this attribute.
375 *
376 * @return the value of the attribute {@code tabindex}
377 * or an empty string if that attribute isn't defined.
378 */
379 public final String getTabIndexAttribute() {
380 return getAttributeDirect("tabindex");
381 }
382
383 /**
384 * Returns the value of the attribute {@code accesskey}. Refer to the
385 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
386 * documentation for details on the use of this attribute.
387 *
388 * @return the value of the attribute {@code accesskey}
389 * or an empty string if that attribute isn't defined.
390 */
391 public final String getAccessKeyAttribute() {
392 return getAttributeDirect("accesskey");
393 }
394
395 /**
396 * Returns the value of the attribute {@code onfocus}. Refer to the
397 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
398 * documentation for details on the use of this attribute.
399 *
400 * @return the value of the attribute {@code onfocus}
401 * or an empty string if that attribute isn't defined.
402 */
403 public final String getOnFocusAttribute() {
404 return getAttributeDirect("onfocus");
405 }
406
407 /**
408 * Returns the value of the attribute {@code onblur}. Refer to the
409 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
410 * documentation for details on the use of this attribute.
411 *
412 * @return the value of the attribute {@code onblur}
413 * or an empty string if that attribute isn't defined.
414 */
415 public final String getOnBlurAttribute() {
416 return getAttributeDirect("onblur");
417 }
418
419 /**
420 * Returns the value of the attribute {@code onselect}. Refer to the
421 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
422 * documentation for details on the use of this attribute.
423 *
424 * @return the value of the attribute {@code onselect}
425 * or an empty string if that attribute isn't defined.
426 */
427 public final String getOnSelectAttribute() {
428 return getAttributeDirect("onselect");
429 }
430
431 /**
432 * Returns the value of the attribute {@code onchange}. Refer to the
433 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
434 * documentation for details on the use of this attribute.
435 *
436 * @return the value of the attribute {@code onchange}
437 * or an empty string if that attribute isn't defined.
438 */
439 public final String getOnChangeAttribute() {
440 return getAttributeDirect("onchange");
441 }
442
443 /**
444 * Returns the value of the attribute {@code accept}. Refer to the
445 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
446 * documentation for details on the use of this attribute.
447 *
448 * @return the value of the attribute {@code accept}
449 * or an empty string if that attribute isn't defined.
450 */
451 public final String getAcceptAttribute() {
452 return getAttribute(HttpHeader.ACCEPT_LC);
453 }
454
455 /**
456 * Returns the value of the attribute {@code align}. Refer to the
457 * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
458 * documentation for details on the use of this attribute.
459 *
460 * @return the value of the attribute {@code align}
461 * or an empty string if that attribute isn't defined.
462 */
463 public final String getAlignAttribute() {
464 return getAttributeDirect("align");
465 }
466
467 /**
468 * {@inheritDoc}
469 * @see SubmittableElement#reset()
470 */
471 @Override
472 public void reset() {
473 setValue(getDefaultValue());
474 isValueDirty_ = true;
475 }
476
477 /**
478 * {@inheritDoc}
479 *
480 * @see SubmittableElement#setDefaultValue(String)
481 */
482 @Override
483 public void setDefaultValue(final String defaultValue) {
484 setValueAttribute(defaultValue);
485 }
486
487 /**
488 * {@inheritDoc}
489 * @see SubmittableElement#getDefaultValue()
490 */
491 @Override
492 public String getDefaultValue() {
493 return getValueAttribute();
494 }
495
496 /**
497 * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
498 *
499 * @return the raw value
500 */
501 public String getRawValue() {
502 return rawValue_;
503 }
504
505 /**
506 * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
507 *
508 * Update the raw value.
509 * @param rawValue the new raw value
510 */
511 public void setRawValue(final String rawValue) {
512 rawValue_ = rawValue;
513 }
514
515 /**
516 * {@inheritDoc} The default implementation returns {@code false}; only checkboxes and
517 * radio buttons really care what the default checked value is.
518 * @see SubmittableElement#isDefaultChecked()
519 * @see HtmlRadioButtonInput#isDefaultChecked()
520 * @see HtmlCheckBoxInput#isDefaultChecked()
521 */
522 @Override
523 public boolean isDefaultChecked() {
524 return false;
525 }
526
527 /**
528 * Sets the {@code checked} attribute, returning the page that occupies this input's window after setting
529 * the attribute. Note that the returned page may or may not be the original page, depending on
530 * the presence of JavaScript event handlers, etc.
531 *
532 * @param isChecked {@code true} if this element is to be selected
533 * @return the page that occupies this input's window after setting the attribute
534 */
535 public Page setChecked(final boolean isChecked) {
536 // By default, this returns the current page. Derived classes will override.
537 return getPage();
538 }
539
540 /**
541 * Sets the {@code readOnly} attribute.
542 *
543 * @param isReadOnly {@code true} if this element is read only
544 */
545 public void setReadOnly(final boolean isReadOnly) {
546 if (isReadOnly) {
547 setAttribute("readonly", "");
548 }
549 else {
550 removeAttribute("readonly");
551 }
552 }
553
554 /**
555 * Returns {@code true} if this element is currently selected.
556 * @return {@code true} if this element is currently selected
557 */
558 public boolean isChecked() {
559 return hasAttribute(ATTRIBUTE_CHECKED);
560 }
561
562 /**
563 * Returns {@code true} if this element is read only.
564 * @return {@code true} if this element is read only
565 */
566 public boolean isReadOnly() {
567 return hasAttribute("readOnly");
568 }
569
570 /**
571 * {@inheritDoc}
572 */
573 @Override
574 protected boolean propagateClickStateUpdateToParent() {
575 return true;
576 }
577
578 /**
579 * {@inheritDoc}
580 */
581 @Override
582 public boolean handles(final Event event) {
583 if (event instanceof MouseEvent) {
584 return true;
585 }
586
587 return super.handles(event);
588 }
589
590 /**
591 * Executes the onchange script code for this element if this is appropriate.
592 * This means that the element must have an onchange script, script must be enabled
593 * and the change in the element must not have been triggered by a script.
594 *
595 * @param htmlElement the element that contains the onchange attribute
596 * @return the page that occupies this window after this method completes (may or
597 * may not be the same as the original page)
598 */
599 static Page executeOnChangeHandlerIfAppropriate(final HtmlElement htmlElement) {
600 final SgmlPage page = htmlElement.getPage();
601 final WebClient webClient = page.getWebClient();
602
603 if (!webClient.isJavaScriptEngineEnabled()) {
604 return page;
605 }
606
607 final AbstractJavaScriptEngine<?> engine = webClient.getJavaScriptEngine();
608 if (engine.isScriptRunning()) {
609 return page;
610 }
611 final ScriptResult scriptResult = htmlElement.fireEvent(Event.TYPE_CHANGE);
612
613 if (webClient.containsWebWindow(page.getEnclosingWindow())) {
614 // may be itself or a newly loaded one
615 return page.getEnclosingWindow().getEnclosedPage();
616 }
617
618 if (scriptResult != null) {
619 // current window doesn't exist anymore
620 return webClient.getCurrentWindow().getEnclosedPage();
621 }
622
623 return page;
624 }
625
626 /**
627 * {@inheritDoc}
628 */
629 @Override
630 protected void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue,
631 final boolean notifyAttributeChangeListeners, final boolean notifyMutationObservers) {
632 final String qualifiedNameLC = StringUtils.toRootLowerCase(qualifiedName);
633
634 if (TYPE_ATTRIBUTE.equals(qualifiedNameLC)) {
635 changeType(attributeValue, true);
636 return;
637 }
638
639 if (VALUE_ATTRIBUTE.equals(qualifiedNameLC)) {
640 super.setAttributeNS(namespaceURI, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
641 notifyMutationObservers);
642
643 valueAttributeChanged(attributeValue, isValueDirty_);
644 return;
645 }
646
647 super.setAttributeNS(namespaceURI, qualifiedNameLC, attributeValue, notifyAttributeChangeListeners,
648 notifyMutationObservers);
649 }
650
651 /**
652 * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
653 *
654 * Marks this element as modified (value) by javascript. This is needed
655 * to support maxlength/minlength validation.
656 */
657 public void valueModifiedByJavascript() {
658 valueModifiedByJavascript_ = true;
659 }
660
661 /**
662 * {@inheritDoc}
663 */
664 @Override
665 public final void focus() {
666 super.focus();
667 // store current value to trigger onchange when needed at focus lost
668 valueAtFocus_ = getInternalValue();
669 }
670
671 /**
672 * {@inheritDoc}
673 */
674 @Override
675 public final void removeFocus() {
676 super.removeFocus();
677
678 if (valueAtFocus_ != null && !valueAtFocus_.equals(getInternalValue())) {
679 handleFocusLostValueChanged();
680 }
681 valueAtFocus_ = null;
682 }
683
684 void handleFocusLostValueChanged() {
685 executeOnChangeHandlerIfAppropriate(this);
686 }
687
688 /**
689 * Returns returns the raw value.
690 *
691 * @return returns the raw value
692 */
693 protected Object getInternalValue() {
694 return getRawValue();
695 }
696
697 /**
698 * {@inheritDoc}
699 */
700 @Override
701 public DisplayStyle getDefaultStyleDisplay() {
702 return DisplayStyle.INLINE_BLOCK;
703 }
704
705 /**
706 * Returns the value of the {@code size} attribute.
707 *
708 * @return the value of the {@code size} attribute
709 */
710 public String getSize() {
711 return getAttributeDirect("size");
712 }
713
714 /**
715 * Sets the {@code size} attribute.
716 *
717 * @param size the {@code size} attribute
718 */
719 public void setSize(final String size) {
720 setAttribute("size", size);
721 }
722
723 /**
724 * Sets the {@code maxLength} attribute.
725 *
726 * @param maxLength the {@code maxLength} attribute
727 */
728 public void setMaxLength(final int maxLength) {
729 setAttribute("maxLength", String.valueOf(maxLength));
730 }
731
732 /**
733 * Sets the {@code minLength} attribute.
734 *
735 * @param minLength the {@code minLength} attribute
736 */
737 public void setMinLength(final int minLength) {
738 setAttribute("minLength", String.valueOf(minLength));
739 }
740
741 /**
742 * Returns the value of the {@code accept} attribute.
743 *
744 * @return the value of the {@code accept} attribute
745 */
746 public String getAccept() {
747 return getAttribute(HttpHeader.ACCEPT_LC);
748 }
749
750 /**
751 * Sets the {@code accept} attribute.
752 *
753 * @param accept the {@code accept} attribute
754 */
755 public void setAccept(final String accept) {
756 setAttribute(HttpHeader.ACCEPT_LC, accept);
757 }
758
759 /**
760 * Returns the value of the {@code autocomplete} attribute.
761 *
762 * @return the value of the {@code autocomplete} attribute
763 */
764 public String getAutocomplete() {
765 return getAttributeDirect("autocomplete");
766 }
767
768 /**
769 * Sets the {@code autocomplete} attribute.
770 *
771 * @param autocomplete the {@code autocomplete} attribute
772 */
773 public void setAutocomplete(final String autocomplete) {
774 setAttribute("autocomplete", autocomplete);
775 }
776
777 /**
778 * Returns the value of the {@code placeholder} attribute.
779 *
780 * @return the value of the {@code placeholder} attribute
781 */
782 public String getPlaceholder() {
783 return getAttributeDirect("placeholder");
784 }
785
786 /**
787 * Sets the {@code placeholder} attribute.
788 *
789 * @param placeholder the {@code placeholder} attribute
790 */
791 public void setPlaceholder(final String placeholder) {
792 setAttribute("placeholder", placeholder);
793 }
794
795 /**
796 * Returns the value of the {@code pattern} attribute.
797 *
798 * @return the value of the {@code pattern} attribute
799 */
800 public String getPattern() {
801 return getAttributeDirect("pattern");
802 }
803
804 /**
805 * Sets the {@code pattern} attribute.
806 *
807 * @param pattern the {@code pattern} attribute
808 */
809 public void setPattern(final String pattern) {
810 setAttribute("pattern", pattern);
811 }
812
813 /**
814 * Returns the value of the {@code min} attribute.
815 *
816 * @return the value of the {@code min} attribute
817 */
818 public String getMin() {
819 return getAttributeDirect("min");
820 }
821
822 /**
823 * Sets the {@code min} attribute.
824 *
825 * @param min the {@code min} attribute
826 */
827 public void setMin(final String min) {
828 setAttribute("min", min);
829 }
830
831 /**
832 * Returns the value of the {@code max} attribute.
833 *
834 * @return the value of the {@code max} attribute
835 */
836 public String getMax() {
837 return getAttributeDirect("max");
838 }
839
840 /**
841 * Sets the {@code max} attribute.
842 *
843 * @param max the {@code max} attribute
844 */
845 public void setMax(final String max) {
846 setAttribute("max", max);
847 }
848
849 /**
850 * Returns the value of the {@code step} attribute.
851 *
852 * @return the value of the {@code step} attribute
853 */
854 public String getStep() {
855 return getAttributeDirect("step");
856 }
857
858 /**
859 * Sets the {@code step} attribute.
860 *
861 * @param step the {@code step} attribute
862 */
863 public void setStep(final String step) {
864 setAttribute("step", step);
865 }
866
867 @Override
868 public boolean isValid() {
869 return !isValueMissingValidityState()
870 && isCustomValidityValid()
871 && isMaxLengthValid() && isMinLengthValid()
872 && !hasPatternMismatchValidityState();
873 }
874
875 protected boolean isCustomValidityValid() {
876 if (isCustomErrorValidityState()) {
877 final String type = getAttributeDirect(TYPE_ATTRIBUTE).toLowerCase(Locale.ROOT);
878 if (!"button".equals(type)
879 && !"hidden".equals(type)
880 && !"reset".equals(type)
881 && !("image".equals(type) && hasFeature(HTMLINPUT_TYPE_IMAGE_IGNORES_CUSTOM_VALIDITY))) {
882 return false;
883 }
884 }
885 return true;
886 }
887
888 @Override
889 protected boolean isRequiredSupported() {
890 return true;
891 }
892
893 /**
894 * Returns if the input element supports pattern validation. Refer to the
895 * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a> documentation
896 * for details.
897 * @return if the input element supports pattern validation
898 */
899 protected boolean isPatternSupported() {
900 return false;
901 }
902
903 /**
904 * Returns if the element executes pattern validation on blank strings.
905 *
906 * @return if the element executes pattern validation on blank strings
907 */
908 protected boolean isBlankPatternValidated() {
909 return true;
910 }
911
912 /**
913 * Returns if the input element supports maxlength minlength validation. Refer to the
914 * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a> documentation
915 * for details.
916 * @return if the input element supports pattern validation
917 */
918 protected boolean isMinMaxLengthSupported() {
919 return false;
920 }
921
922 /**
923 * Returns if the input element has a maximum allowed value length. Refer to the
924 * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a>
925 * documentation for details.
926 *
927 * @return if the input element has a maximum allowed value length
928 */
929 private boolean isMaxLengthValid() {
930 if (!isMinMaxLengthSupported()
931 || valueModifiedByJavascript_
932 || getMaxLength() == Integer.MAX_VALUE
933 || getDefaultValue().equals(getValue())) {
934 return true;
935 }
936
937 return getValue().length() <= getMaxLength();
938 }
939
940 /**
941 * Returns if the input element has a minimum allowed value length. Refer to the
942 * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a>
943 * documentation for details.
944 *
945 * @return if the input element has a minimum allowed value length
946 */
947 private boolean isMinLengthValid() {
948 if (!isMinMaxLengthSupported()
949 || valueModifiedByJavascript_
950 || getMinLength() == Integer.MIN_VALUE
951 || getDefaultValue().equals(getValue())) {
952 return true;
953 }
954
955 return getValue().length() >= getMinLength();
956 }
957
958 /**
959 * Returns if the input element has a valid value pattern. Refer to the
960 * <a href="https://www.w3.org/TR/html5/sec-forms.html">HTML 5</a> documentation
961 * for details.
962 *
963 * @return if the input element has a valid value pattern
964 */
965 private boolean isPatternValid() {
966 if (!isPatternSupported()) {
967 return true;
968 }
969
970 final String pattern = getPattern();
971 if (StringUtils.isEmptyOrNull(pattern)) {
972 return true;
973 }
974
975 final String value = getValue();
976 if (StringUtils.isEmptyOrNull(value)) {
977 return true;
978 }
979 if (!isBlankPatternValidated() && StringUtils.isBlank(value)) {
980 return true;
981 }
982
983 try (Context cx = HtmlUnitContextFactory.getGlobal().enterContext()) {
984 RegExpEngineAccess.compile(cx, pattern, "");
985 final RegExpEngineAccess.CompiledRegExp compiled
986 = RegExpEngineAccess.compile(cx, "^(?:" + pattern + ")$", "");
987
988 return RegExpEngineAccess.matches(cx, value, compiled);
989 }
990 catch (final Exception ignored) {
991 // ignore if regex invalid
992 }
993 return true;
994 }
995
996 /**
997 * {@inheritDoc}
998 */
999 @Override
1000 public boolean willValidate() {
1001 return !isDisabled() && !isReadOnly();
1002 }
1003
1004 /**
1005 * {@inheritDoc}
1006 */
1007 @Override
1008 public void setCustomValidity(final String message) {
1009 customValidity_ = message;
1010 }
1011
1012 /**
1013 * Returns whether this is a checkbox or a radio button.
1014 *
1015 * @return whether this is a checkbox or a radio button
1016 */
1017 public boolean isCheckable() {
1018 final String type = getAttributeDirect(TYPE_ATTRIBUTE);
1019 return "radio".equalsIgnoreCase(type) || "checkbox".equalsIgnoreCase(type);
1020 }
1021
1022 /**
1023 * Returns false for type submit/reset/image/button otherwise true.
1024 *
1025 * @return false for type submit/reset/image/button otherwise true
1026 */
1027 public boolean isSubmitable() {
1028 final String type = getAttributeDirect(TYPE_ATTRIBUTE);
1029 return !"submit".equalsIgnoreCase(type)
1030 && !"image".equalsIgnoreCase(type)
1031 && !"reset".equalsIgnoreCase(type)
1032 && !"button".equalsIgnoreCase(type);
1033 }
1034
1035 @Override
1036 public boolean isCustomErrorValidityState() {
1037 return !StringUtils.isEmptyOrNull(customValidity_);
1038 }
1039
1040 @Override
1041 public boolean hasPatternMismatchValidityState() {
1042 return !isPatternValid();
1043 }
1044
1045 @Override
1046 public boolean isTooShortValidityState() {
1047 if (!isMinMaxLengthSupported()
1048 || valueModifiedByJavascript_
1049 || getMinLength() == Integer.MIN_VALUE
1050 || getDefaultValue().equals(getValue())) {
1051 return false;
1052 }
1053
1054 return getValue().length() < getMinLength();
1055 }
1056
1057 // no need to override isTooLongValidityState()
1058 // The HTML spec (§4.10.18.5) has a deliberate rule: tooLong only fires
1059 // if the user has interacted with the field ("the element has a dirty value flag").
1060 // A value set via JS (elem.value = '...') that was never touched by the user does
1061 // not set the dirty flag, so tooLong stays false regardless of the value length.
1062 // see HtmlTextInputTest
1063 // maxLengthValidationInvalid()/maxLengthValidationInvalidInitial()/maxLengthValidationValid()
1064 // @Override
1065 // public boolean isTooLongValidityState() {
1066 // return false;
1067 // }
1068
1069 @Override
1070 public boolean isValidValidityState() {
1071 return !isCustomErrorValidityState()
1072 && !isValueMissingValidityState()
1073 && !isTooLongValidityState()
1074 && !isTooShortValidityState()
1075 && !hasPatternMismatchValidityState();
1076 }
1077
1078 @Override
1079 public boolean isValueMissingValidityState() {
1080 return isRequiredSupported()
1081 && ATTRIBUTE_NOT_DEFINED != getAttributeDirect(ATTRIBUTE_REQUIRED)
1082 && getValue().isEmpty();
1083 }
1084
1085 /**
1086 * Returns the value of the attribute {@code formnovalidate} or an empty string if that attribute isn't defined.
1087 *
1088 * @return the value of the attribute {@code formnovalidate} or an empty string if that attribute isn't defined
1089 */
1090 public final boolean isFormNoValidate() {
1091 return hasAttribute(ATTRIBUTE_FORMNOVALIDATE);
1092 }
1093
1094 /**
1095 * Sets the value of the attribute {@code formnovalidate}.
1096 *
1097 * @param noValidate the value of the attribute {@code formnovalidate}
1098 */
1099 public final void setFormNoValidate(final boolean noValidate) {
1100 if (noValidate) {
1101 setAttribute(ATTRIBUTE_FORMNOVALIDATE, ATTRIBUTE_FORMNOVALIDATE);
1102 }
1103 else {
1104 removeAttribute(ATTRIBUTE_FORMNOVALIDATE);
1105 }
1106 }
1107
1108 /**
1109 * Returns the {@code type} property.
1110 *
1111 * @return the {@code type} property
1112 */
1113 public final String getType() {
1114 final BrowserVersion browserVersion = getPage().getWebClient().getBrowserVersion();
1115 String type = getTypeAttribute();
1116 type = StringUtils.toRootLowerCase(type);
1117 return isSupported(type, browserVersion) ? type : "text";
1118 }
1119
1120 /**
1121 * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
1122 *
1123 * Changes the type of the current HtmlInput. Because there are several subclasses of HtmlInput,
1124 * changing the type attribute is not sufficient, this will replace the HtmlInput element in the
1125 * DOM tree with a new one (at least of the newType is different from the old one).<br>
1126 * The js peer object is still the same (there is only a HTMLInputElement without any sublcasses).<br>
1127 * This returns the new (or the old) HtmlInput element to ease the use of this method.
1128 * @param newType the new type to set
1129 * @param setThroughAttribute set type value through setAttribute()
1130 * @return the new or the old HtmlInput element
1131 */
1132 public HtmlInput changeType(String newType, final boolean setThroughAttribute) {
1133 final String currentType = getAttributeDirect(TYPE_ATTRIBUTE);
1134
1135 final SgmlPage page = getPage();
1136 final WebClient webClient = page.getWebClient();
1137 final BrowserVersion browser = webClient.getBrowserVersion();
1138 if (!currentType.equalsIgnoreCase(newType)) {
1139 if (!isSupported(StringUtils.toRootLowerCase(newType), browser)) {
1140 if (setThroughAttribute) {
1141 newType = "text";
1142 }
1143 }
1144
1145 final AttributesImpl attributes = new AttributesImpl();
1146 boolean typeFound = false;
1147 for (final DomAttr entry : getAttributesMap().values()) {
1148 final String name = entry.getName();
1149 final String value = entry.getValue();
1150
1151 if (TYPE_ATTRIBUTE.equals(name)) {
1152 attributes.addAttribute(null, name, name, null, newType);
1153 typeFound = true;
1154 }
1155 else {
1156 attributes.addAttribute(null, name, name, null, value);
1157 }
1158 }
1159
1160 if (!typeFound) {
1161 attributes.addAttribute(null, TYPE_ATTRIBUTE, TYPE_ATTRIBUTE, null, newType);
1162 }
1163
1164 // create a new one only if we have a new type
1165 if (ATTRIBUTE_NOT_DEFINED != currentType || !"text".equalsIgnoreCase(newType)) {
1166 final HtmlInput newInput = (HtmlInput) webClient.getPageCreator().getHtmlParser()
1167 .getFactory(TAG_NAME)
1168 .createElement(page, TAG_NAME, attributes);
1169
1170 newInput.adjustValueAfterTypeChange(this, browser);
1171
1172 // the input hasn't yet been inserted into the DOM tree (likely has been
1173 // created via document.createElement()), so simply replace it with the
1174 // new Input instance created in the code above
1175 if (getParentNode() != null) {
1176 getParentNode().replaceChild(newInput, this);
1177 }
1178
1179 final WebClient client = page.getWebClient();
1180 if (client.isJavaScriptEngineEnabled()) {
1181 final HTMLInputElement scriptable = getScriptableObject();
1182 setScriptableObject(null);
1183 scriptable.setDomNode(newInput, true);
1184 }
1185
1186 return newInput;
1187 }
1188 super.setAttributeNS(null, TYPE_ATTRIBUTE, newType, true, true);
1189 }
1190 return this;
1191 }
1192
1193 protected void adjustValueAfterTypeChange(final HtmlInput oldInput, final BrowserVersion browserVersion) {
1194 final String originalValue = oldInput.getValue();
1195 if (ATTRIBUTE_NOT_DEFINED != originalValue) {
1196 setValue(originalValue);
1197 }
1198 }
1199
1200 /**
1201 * Returns whether the specified type is supported or not.
1202 * @param type the input type
1203 * @param browserVersion the browser version
1204 * @return whether the specified type is supported or not
1205 */
1206 private static boolean isSupported(final String type, final BrowserVersion browserVersion) {
1207 boolean supported = false;
1208 switch (type) {
1209 case "month":
1210 supported = browserVersion.hasFeature(HTMLINPUT_TYPE_MONTH_SUPPORTED);
1211 break;
1212 case "week":
1213 supported = browserVersion.hasFeature(HTMLINPUT_TYPE_WEEK_SUPPORTED);
1214 break;
1215 case "color":
1216 case "date":
1217 case "datetime-local":
1218 case "time":
1219 case "email":
1220 case "text":
1221 case "submit":
1222 case "checkbox":
1223 case "radio":
1224 case "hidden":
1225 case "password":
1226 case "image":
1227 case "reset":
1228 case "button":
1229 case "file":
1230 case "number":
1231 case "range":
1232 case "search":
1233 case "tel":
1234 case "url":
1235 supported = true;
1236 break;
1237
1238 default:
1239 }
1240 return supported;
1241 }
1242
1243 protected void unmarkValueDirty() {
1244 isValueDirty_ = false;
1245 }
1246
1247 protected void markValueDirty() {
1248 isValueDirty_ = true;
1249 }
1250 }