View Javadoc
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.javascript.host.html;
16  
17  import static org.htmlunit.BrowserVersionFeatures.JS_INPUT_NUMBER_DOT_AT_END_IS_DOUBLE;
18  import static org.htmlunit.javascript.configuration.SupportedBrowser.FF;
19  import static org.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
20  
21  import java.io.IOException;
22  
23  import org.apache.commons.lang3.StringUtils;
24  import org.apache.commons.lang3.math.NumberUtils;
25  import org.htmlunit.html.DomElement;
26  import org.htmlunit.html.DomNode;
27  import org.htmlunit.html.HtmlCheckBoxInput;
28  import org.htmlunit.html.HtmlFileInput;
29  import org.htmlunit.html.HtmlInput;
30  import org.htmlunit.html.HtmlNumberInput;
31  import org.htmlunit.html.HtmlRadioButtonInput;
32  import org.htmlunit.html.HtmlTextInput;
33  import org.htmlunit.html.impl.SelectableTextInput;
34  import org.htmlunit.javascript.JavaScriptEngine;
35  import org.htmlunit.javascript.configuration.JsxClass;
36  import org.htmlunit.javascript.configuration.JsxConstructor;
37  import org.htmlunit.javascript.configuration.JsxFunction;
38  import org.htmlunit.javascript.configuration.JsxGetter;
39  import org.htmlunit.javascript.configuration.JsxSetter;
40  import org.htmlunit.javascript.host.dom.DOMException;
41  import org.htmlunit.javascript.host.dom.NodeList;
42  import org.htmlunit.javascript.host.event.Event;
43  import org.htmlunit.javascript.host.file.FileList;
44  
45  /**
46   * The JavaScript object for {@link HtmlInput}.
47   *
48   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
49   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
50   * @author Marc Guillemot
51   * @author Chris Erskine
52   * @author Ahmed Ashour
53   * @author Daniel Gredler
54   * @author Ronald Brill
55   * @author Frank Danek
56   * @author Anton Demydenko
57   */
58  @JsxClass(domClass = HtmlInput.class)
59  public class HTMLInputElement extends HTMLElement {
60  
61      /** "Live" labels collection; has to be a member to have equality (==) working. */
62      private NodeList labels_;
63  
64      /**
65       * JavaScript constructor.
66       */
67      @Override
68      @JsxConstructor
69      public void jsConstructor() {
70          super.jsConstructor();
71      }
72  
73      /**
74       * Returns the {@code type} property.
75       * @return the {@code type} property
76       */
77      @JsxGetter
78      public String getType() {
79          return getDomNodeOrDie().getType();
80      }
81  
82      /**
83       * Sets the value of the attribute {@code type}.
84       * Note: this replace the DOM node with a new one.
85       * @param newType the new type to set
86       */
87      @JsxSetter
88      public void setType(final String newType) {
89          getDomNodeOrDie().changeType(newType, false);
90      }
91  
92      /**
93       * Sets the value of the JavaScript attribute {@code value}.
94       *
95       * @param newValue the new value
96       */
97      @JsxSetter
98      @Override
99      public void setValue(final Object newValue) {
100         if (null == newValue) {
101             getDomNodeOrDie().setValue("");
102             getDomNodeOrDie().valueModifiedByJavascript();
103             return;
104         }
105 
106         final String val = JavaScriptEngine.toString(newValue);
107         if ("file".equalsIgnoreCase(getType())) {
108             if (StringUtils.isNotEmpty(val)) {
109                 throw JavaScriptEngine.asJavaScriptException(
110                         getWindow(),
111                         "Failed to set the 'value' property on 'HTMLInputElement'.",
112                         DOMException.INVALID_STATE_ERR);
113             }
114             return;
115         }
116 
117         getDomNodeOrDie().setValue(val);
118         getDomNodeOrDie().valueModifiedByJavascript();
119     }
120 
121     /**
122      * Sets the checked property. Although this property is defined in Input it
123      * doesn't make any sense for input's other than checkbox and radio. This
124      * implementation does nothing. The implementations in Checkbox and Radio
125      * actually do the work.
126      *
127      * @param checked True if this input should have the {@code checked} attribute set
128      */
129     @JsxSetter
130     public void setChecked(final boolean checked) {
131         getDomNodeOrDie().setChecked(checked);
132     }
133 
134     /**
135      * {@inheritDoc}
136      */
137     @Override
138     public HtmlInput getDomNodeOrDie() {
139         return (HtmlInput) super.getDomNodeOrDie();
140     }
141 
142     /**
143      * Returns the value of the checked property. Although this property is
144      * defined in Input it doesn't make any sense for input's other than
145      * checkbox and radio. This implementation does nothing. The
146      * implementations in Checkbox and Radio actually do the work.
147      *
148      * @return the checked property
149      */
150     @JsxGetter
151     public boolean isChecked() {
152         return getDomNodeOrDie().isChecked();
153     }
154 
155     /**
156      * Select this element.
157      */
158     @JsxFunction
159     public void select() {
160         final HtmlInput input = getDomNodeOrDie();
161         if (input instanceof HtmlTextInput) {
162             ((HtmlTextInput) input).select();
163         }
164         // currently nothing for other input types
165     }
166 
167     /**
168      * Returns the input's default value, used if the containing form gets reset.
169      * @return the input's default value, used if the containing form gets reset
170      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533718.aspx">MSDN Documentation</a>
171      */
172     @JsxGetter
173     public String getDefaultValue() {
174         return getDomNodeOrDie().getDefaultValue();
175     }
176 
177     /**
178      * Sets the input's default value, used if the containing form gets reset.
179      * @param defaultValue the input's default value, used if the containing form gets reset
180      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533718.aspx">MSDN Documentation</a>
181      */
182     @JsxSetter
183     public void setDefaultValue(final String defaultValue) {
184         getDomNodeOrDie().setDefaultValue(defaultValue);
185     }
186 
187     /**
188      * Returns the input's default checked value, used if the containing form gets reset.
189      * @return the input's default checked value, used if the containing form gets reset
190      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533715.aspx">MSDN Documentation</a>
191      */
192     @JsxGetter
193     public boolean isDefaultChecked() {
194         return getDomNodeOrDie().isDefaultChecked();
195     }
196 
197     /**
198      * Sets the input's default checked value, used if the containing form gets reset.
199      * @param defaultChecked the input's default checked value, used if the containing form gets reset
200      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533715.aspx">MSDN Documentation</a>
201      */
202     @JsxSetter
203     public void setDefaultChecked(final boolean defaultChecked) {
204         getDomNodeOrDie().setDefaultChecked(defaultChecked);
205     }
206 
207     /**
208      * Gets the value of {@code textLength} attribute.
209      * @return the text length
210      */
211     @JsxGetter({FF, FF_ESR})
212     public int getTextLength() {
213         return getValue().length();
214     }
215 
216     /**
217      * Gets the value of {@code selectionStart} attribute.
218      * @return the selection start
219      */
220     @JsxGetter
221     public Integer getSelectionStart() {
222         final DomNode dom = getDomNodeOrDie();
223         if (dom instanceof SelectableTextInput) {
224             if ("number".equalsIgnoreCase(getType())) {
225                 return null;
226             }
227 
228             return ((SelectableTextInput) dom).getSelectionStart();
229         }
230 
231         return null;
232     }
233 
234     /**
235      * Sets the value of {@code selectionStart} attribute.
236      * @param start selection start
237      */
238     @JsxSetter
239     public void setSelectionStart(final int start) {
240         final DomNode dom = getDomNodeOrDie();
241         if (dom instanceof SelectableTextInput) {
242             if ("number".equalsIgnoreCase(getType())) {
243                 throw JavaScriptEngine.asJavaScriptException(
244                         getWindow(),
245                         "Failed to set the 'selectionStart' property"
246                                 + "from 'HTMLInputElement': "
247                                 + "The input element's type ('number') does not support selection.",
248                         DOMException.INVALID_STATE_ERR);
249             }
250 
251             ((SelectableTextInput) dom).setSelectionStart(start);
252             return;
253         }
254 
255         throw JavaScriptEngine.asJavaScriptException(
256                 getWindow(),
257                 "Failed to set the 'selectionStart' property from 'HTMLInputElement': "
258                         + "The input element's type (" + getType() + ") does not support selection.",
259                 DOMException.INVALID_STATE_ERR);
260     }
261 
262     /**
263      * Gets the value of {@code selectionEnd} attribute.
264      * @return the selection end
265      */
266     @JsxGetter
267     public Integer getSelectionEnd() {
268         final DomNode dom = getDomNodeOrDie();
269         if (dom instanceof SelectableTextInput) {
270             if ("number".equalsIgnoreCase(getType())) {
271                 return null;
272             }
273 
274             return ((SelectableTextInput) dom).getSelectionEnd();
275         }
276 
277         return null;
278     }
279 
280     /**
281      * Sets the value of {@code selectionEnd} attribute.
282      * @param end selection end
283      */
284     @JsxSetter
285     public void setSelectionEnd(final int end) {
286         final DomNode dom = getDomNodeOrDie();
287         if (dom instanceof SelectableTextInput) {
288             if ("number".equalsIgnoreCase(getType())) {
289                 throw JavaScriptEngine.asJavaScriptException(
290                         getWindow(),
291                         "Failed to set the 'selectionEnd' property"
292                                 + "from 'HTMLInputElement': "
293                                 + "The input element's type ('number') does not support selection.",
294                         DOMException.INVALID_STATE_ERR);
295             }
296 
297             ((SelectableTextInput) dom).setSelectionEnd(end);
298             return;
299         }
300 
301         throw JavaScriptEngine.asJavaScriptException(
302                 getWindow(),
303                 "Failed to set the 'selectionEnd' property from 'HTMLInputElement': "
304                         + "The input element's type (" + getType() + ") does not support selection.",
305                 DOMException.INVALID_STATE_ERR);
306     }
307 
308     /**
309      * Gets the max length.
310      * @return the max length
311      */
312     @JsxGetter
313     public int getMaxLength() {
314         final String attrValue = getDomNodeOrDie().getAttribute("maxLength");
315         return NumberUtils.toInt(attrValue, -1);
316     }
317 
318     /**
319      * Sets the value of {@code maxLength} attribute.
320      * @param length the new value
321      */
322     @JsxSetter
323     public void setMaxLength(final int length) {
324         getDomNodeOrDie().setMaxLength(length);
325     }
326 
327     /**
328      * Gets the {@code minLength}.
329      * @return the {@code minLength}
330      */
331     @JsxGetter
332     public int getMinLength() {
333         final String attrValue = getDomNodeOrDie().getAttribute("minLength");
334         return NumberUtils.toInt(attrValue, -1);
335     }
336 
337     /**
338      * Sets the value of {@code minLength} attribute.
339      * @param length the new value
340      */
341     @JsxSetter
342     public void setMinLength(final int length) {
343         getDomNodeOrDie().setMinLength(length);
344     }
345 
346     /**
347      * Gets the {@code min} property.
348      * @return the {@code min} property
349      */
350     @JsxGetter
351     public String getMin() {
352         return getDomNodeOrDie().getAttributeDirect("min");
353     }
354 
355     /**
356      * Sets the {@code min} property.
357      * @param min the {@code min} property
358      */
359     @JsxSetter
360     public void setMin(final String min) {
361         getDomNodeOrDie().setAttribute("min", min);
362     }
363 
364     /**
365      * Gets the {@code max} property.
366      * @return the {@code max} property
367      */
368     @JsxGetter
369     public String getMax() {
370         return getDomNodeOrDie().getAttributeDirect("max");
371     }
372 
373     /**
374      * Sets the {@code max} property.
375      * @param max the {@code max} property
376      */
377     @JsxSetter
378     public void setMax(final String max) {
379         getDomNodeOrDie().setAttribute("max", max);
380     }
381 
382     /**
383      * Gets the {@code step} property.
384      * @return the {@code step} property
385      */
386     @JsxGetter
387     public String getStep() {
388         return getDomNodeOrDie().getAttributeDirect("step");
389     }
390 
391     /**
392      * Sets the {@code step} property.
393      * @param step the {@code step} property
394      */
395     @JsxSetter
396     public void setStep(final String step) {
397         getDomNodeOrDie().setAttribute("step", step);
398     }
399 
400     /**
401      * Gets the value of {@code readOnly} attribute.
402      * @return the readOnly attribute
403      */
404     @JsxGetter
405     public boolean isReadOnly() {
406         return getDomNodeOrDie().isReadOnly();
407     }
408 
409     /**
410      * Sets the value of {@code readOnly} attribute.
411      * @param readOnly the new value
412      */
413     @JsxSetter
414     public void setReadOnly(final boolean readOnly) {
415         getDomNodeOrDie().setReadOnly(readOnly);
416     }
417 
418     /**
419      * Sets the selected portion of this input element.
420      * @param start the index of the first character to select
421      * @param end the index of the character after the selection
422      */
423     @JsxFunction
424     public void setSelectionRange(final int start, final int end) {
425         setSelectionStart(start);
426         setSelectionEnd(end);
427     }
428 
429     /**
430      * Returns the value of the {@code alt} property.
431      * @return the value of the {@code alt} property
432      */
433     @JsxGetter
434     public String getAlt() {
435         return getDomNodeOrDie().getAttributeDirect("alt");
436     }
437 
438     /**
439      * Returns the value of the {@code alt} property.
440      * @param alt the value
441      */
442     @JsxSetter
443     public void setAlt(final String alt) {
444         getDomNodeOrDie().setAttribute("alt", alt);
445     }
446 
447     /**
448      * Returns the value of the {@code align} property.
449      * @return the value of the {@code align} property
450      */
451     @JsxGetter
452     public String getAlign() {
453         return getAlign(true);
454     }
455 
456     /**
457      * Sets the value of the {@code align} property.
458      * @param align the value of the {@code align} property
459      */
460     @JsxSetter
461     public void setAlign(final String align) {
462         setAlign(align, false);
463     }
464 
465     /**
466      * Returns the value of the {@code src} attribute.
467      * @return the value of the {@code src} attribute
468      */
469     @JsxGetter
470     public String getSrc() {
471         return getDomNodeOrDie().getSrc();
472     }
473 
474     /**
475      * Sets the value of the {@code src} attribute.
476      * @param src the new value
477      */
478     @JsxSetter
479     public void setSrc(final String src) {
480         getDomNodeOrDie().setSrcAttribute(src);
481     }
482 
483     /**
484      * Returns the value of the JavaScript attribute {@code value}.
485      *
486      * @return the value of this attribute
487      */
488     @JsxGetter
489     @Override
490     public String getValue() {
491         final HtmlInput htmlInput = getDomNodeOrDie();
492 
493         if (htmlInput instanceof HtmlNumberInput) {
494             final String valueAttr = htmlInput.getValue();
495             if (!valueAttr.isEmpty()) {
496                 if (org.htmlunit.util.StringUtils.equalsChar('-', valueAttr)
497                         || org.htmlunit.util.StringUtils.equalsChar('+', valueAttr)) {
498                     return "";
499                 }
500 
501                 final int lastPos = valueAttr.length() - 1;
502                 if (lastPos >= 0 && valueAttr.charAt(lastPos) == '.') {
503                     if (htmlInput.hasFeature(JS_INPUT_NUMBER_DOT_AT_END_IS_DOUBLE)) {
504                         return "";
505                     }
506                 }
507                 try {
508                     Double.parseDouble(valueAttr);
509                 }
510                 catch (final NumberFormatException e) {
511                     return "";
512                 }
513             }
514         }
515 
516         return htmlInput.getValue();
517     }
518 
519     /**
520      * {@inheritDoc}
521      */
522     @Override
523     public String getAttribute(final String attributeName) {
524         final String superAttribute = super.getAttribute(attributeName);
525         if (DomElement.VALUE_ATTRIBUTE.equalsIgnoreCase(attributeName)) {
526             if ((superAttribute == null || !superAttribute.isEmpty())
527                     && getDefaultValue().isEmpty()) {
528                 return null;
529             }
530             if (!"file".equals(getType())) {
531                 return getDefaultValue();
532             }
533         }
534         return superAttribute;
535     }
536 
537     /**
538      * {@inheritDoc}
539      */
540     @Override
541     public void click() throws IOException {
542         final HtmlInput domNode = getDomNodeOrDie();
543         final boolean originalState = domNode.isChecked();
544 
545         domNode.click(false, false, false, false, false, true, false);
546 
547         final boolean newState = domNode.isChecked();
548 
549         if (originalState != newState
550                 && (domNode instanceof HtmlRadioButtonInput || domNode instanceof HtmlCheckBoxInput)) {
551             domNode.fireEvent(Event.TYPE_CHANGE);
552         }
553     }
554 
555     /**
556      * {@inheritDoc}
557      */
558     @Override
559     protected boolean isEndTagForbidden() {
560         return true;
561     }
562 
563     /**
564      * Returns the {@code required} property.
565      * @return the {@code required} property
566      */
567     @JsxGetter
568     public boolean isRequired() {
569         return getDomNodeOrDie().isRequired();
570     }
571 
572     /**
573      * Sets the {@code required} property.
574      * @param required the new value
575      */
576     @JsxSetter
577     public void setRequired(final boolean required) {
578         getDomNodeOrDie().setRequired(required);
579     }
580 
581     /**
582      * Returns the {@code size} attribute.
583      * @return the {@code size} attribute
584      */
585     @JsxGetter
586     public String getSize() {
587         return getDomNodeOrDie().getSize();
588     }
589 
590     /**
591      * Sets the {@code size} attribute.
592      * @param size the new {@code size} value
593      */
594     @JsxSetter
595     public void setSize(final String size) {
596         getDomNodeOrDie().setSize(size);
597     }
598 
599     /**
600      * Returns the {@code accept} attribute.
601      * @return the {@code accept} attribute
602      */
603     @JsxGetter
604     public String getAccept() {
605         return getDomNodeOrDie().getAccept();
606     }
607 
608     /**
609      * Sets the {@code accept} attribute.
610      * @param accept the new {@code accept} value
611      */
612     @JsxSetter
613     public void setAccept(final String accept) {
614         getDomNodeOrDie().setAccept(accept);
615     }
616 
617     /**
618      * Returns the {@code autocomplete} attribute.
619      * @return the {@code autocomplete} attribute
620      */
621     @JsxGetter
622     public String getAutocomplete() {
623         return getDomNodeOrDie().getAutocomplete();
624     }
625 
626     /**
627      * Sets the {@code autocomplete} attribute.
628      * @param autocomplete the new {@code autocomplete} value
629      */
630     @JsxSetter
631     public void setAutocomplete(final String autocomplete) {
632         getDomNodeOrDie().setAutocomplete(autocomplete);
633     }
634 
635     /**
636      * Returns the {@code files} property.
637      * @return the {@code files} property
638      */
639     @JsxGetter
640     public FileList getFiles() {
641         final HtmlInput htmlInput = getDomNodeOrDie();
642         if (htmlInput instanceof HtmlFileInput) {
643             final FileList list = new FileList(((HtmlFileInput) htmlInput).getFiles());
644             list.setParentScope(getParentScope());
645             list.setPrototype(getPrototype(list.getClass()));
646             return list;
647         }
648         return null;
649     }
650 
651     /**
652      * Returns the {@code placeholder} attribute.
653      * @return the {@code placeholder} attribute
654      */
655     @JsxGetter
656     public String getPlaceholder() {
657         return getDomNodeOrDie().getPlaceholder();
658     }
659 
660     /**
661      * Sets the {@code placeholder} attribute.
662      * @param placeholder the new {@code placeholder} value
663      */
664     @JsxSetter
665     public void setPlaceholder(final String placeholder) {
666         getDomNodeOrDie().setPlaceholder(placeholder);
667     }
668 
669     /**
670      * Returns the {@code width} property.
671      * @return the {@code width} property
672      */
673     @JsxGetter
674     public int getWidth() {
675         final String value = getDomNodeOrDie().getAttributeDirect("width");
676         final Integer intValue = HTMLCanvasElement.getValue(value);
677         if (intValue != null) {
678             return intValue;
679         }
680         return 0;
681     }
682 
683     /**
684      * Sets the {@code width} property.
685      * @param width the {@code width} property
686      */
687     @JsxSetter
688     public void setWidth(final int width) {
689         getDomNodeOrDie().setAttribute("width", Integer.toString(width));
690     }
691 
692     /**
693      * Returns the {@code height} property.
694      * @return the {@code height} property
695      */
696     @JsxGetter
697     public int getHeight() {
698         final String value = getDomNodeOrDie().getAttributeDirect("height");
699         final Integer intValue = HTMLCanvasElement.getValue(value);
700         if (intValue != null) {
701             return intValue;
702         }
703         return 0;
704     }
705 
706     /**
707      * Sets the {@code height} property.
708      * @param height the {@code height} property
709      */
710     @JsxSetter
711     public void setHeight(final int height) {
712         getDomNodeOrDie().setAttribute("height", Integer.toString(height));
713     }
714 
715     /**
716      * Returns the labels associated with the element.
717      * @return the labels associated with the element
718      */
719     @JsxGetter
720     public NodeList getLabels() {
721         if (labels_ == null) {
722             labels_ = new LabelsNodeList(getDomNodeOrDie());
723         }
724         return labels_;
725     }
726 
727     /**
728      * Checks whether the element has any constraints and whether it satisfies them.
729      * @return if the element is valid
730      */
731     @JsxFunction
732     public boolean checkValidity() {
733         return getDomNodeOrDie().isValid();
734     }
735 
736     /**
737      * {@inheritDoc}
738      */
739     @JsxGetter
740     @Override
741     public String getName() {
742         return super.getName();
743     }
744 
745     /**
746      * {@inheritDoc}
747      */
748     @JsxSetter
749     @Override
750     public void setName(final String newName) {
751         super.setName(newName);
752     }
753 
754     /**
755      * {@inheritDoc} Overridden to modify browser configurations.
756      */
757     @Override
758     @JsxGetter
759     public boolean isDisabled() {
760         return super.isDisabled();
761     }
762 
763     /**
764      * {@inheritDoc} Overridden to modify browser configurations.
765      */
766     @Override
767     @JsxSetter
768     public void setDisabled(final boolean disabled) {
769         super.setDisabled(disabled);
770     }
771 
772     /**
773      * {@inheritDoc}
774      */
775     @JsxGetter
776     @Override
777     public HTMLFormElement getForm() {
778         return super.getForm();
779     }
780 
781     /**
782      * @return a ValidityState with the validity states that this element is in.
783      */
784     @JsxGetter
785     public ValidityState getValidity() {
786         final ValidityState validityState = new ValidityState();
787         validityState.setPrototype(getPrototype(validityState.getClass()));
788         validityState.setParentScope(getParentScope());
789         validityState.setDomNode(getDomNodeOrDie());
790         return validityState;
791     }
792 
793     /**
794      * @return whether the element is a candidate for constraint validation
795      */
796     @JsxGetter
797     public boolean getWillValidate() {
798         return getDomNodeOrDie().willValidate();
799     }
800 
801     /**
802      * Sets the custom validity message for the element to the specified message.
803      * @param message the new message
804      */
805     @JsxFunction
806     public void setCustomValidity(final String message) {
807         getDomNodeOrDie().setCustomValidity(message);
808     }
809 
810     /**
811      * Returns the value of the property {@code formnovalidate}.
812      * @return the value of this property
813      */
814     @JsxGetter
815     public boolean isFormNoValidate() {
816         return getDomNodeOrDie().isFormNoValidate();
817     }
818 
819     /**
820      * Sets the value of the property {@code formnovalidate}.
821      * @param value the new value
822      */
823     @JsxSetter
824     public void setFormNoValidate(final boolean value) {
825         getDomNodeOrDie().setFormNoValidate(value);
826     }
827 }