View Javadoc
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 }