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 }