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