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