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