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