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 }