View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.html;
16  
17  import java.io.PrintWriter;
18  import java.util.Map;
19  
20  import org.htmlunit.SgmlPage;
21  import org.htmlunit.html.impl.SelectableTextInput;
22  import org.htmlunit.html.impl.SelectableTextSelectionDelegate;
23  import org.htmlunit.javascript.host.event.Event;
24  import org.htmlunit.javascript.host.event.MouseEvent;
25  import org.htmlunit.util.NameValuePair;
26  import org.htmlunit.util.StringUtils;
27  
28  /**
29   * Wrapper for the HTML element "textarea".
30   *
31   * @author Mike Bowler
32   * @author Barnaby Court
33   * @author David K. Taylor
34   * @author Christian Sell
35   * @author David D. Kilzer
36   * @author Marc Guillemot
37   * @author Daniel Gredler
38   * @author Ahmed Ashour
39   * @author Sudhan Moghe
40   * @author Amit Khanna
41   * @author Ronald Brill
42   * @author Frank Danek
43   * @author Lai Quang Duong
44   */
45  public class HtmlTextArea extends HtmlElement implements DisabledElement, SubmittableElement,
46                  LabelableElement, SelectableTextInput, ValidatableHtmlElement {
47      /** The HTML tag represented by this element. */
48      public static final String TAG_NAME = "textarea";
49  
50      private String defaultValue_;
51  
52      /**
53       * The element's raw value (spec term), decoupled from the DOM child nodes
54       * once {@link #isValueDirty_} is {@code true}. Mirrors {@code HtmlInput}'s
55       * dirty-value-flag model rather than reading/writing child text nodes directly.
56       */
57      private String rawValue_;
58  
59      /**
60       * The dirty value flag (spec term). While {@code false}, the raw value tracks
61       * the element's child text content automatically. Once {@code true} (set by
62       * the {@code value} setter, or by a user edit/type), child mutations no
63       * longer affect the raw value until {@link #reset()} clears the flag again.
64       */
65      private boolean isValueDirty_;
66  
67      private String valueAtFocus_;
68      private String customValidity_;
69  
70      private SelectableTextSelectionDelegate selectionDelegate_ = new SelectableTextSelectionDelegate(this);
71      private DoTypeProcessor doTypeProcessor_ = new DoTypeProcessor(this);
72  
73      /**
74       * Creates an instance.
75       *
76       * @param qualifiedName the qualified name of the element type to instantiate
77       * @param page the page that contains this element
78       * @param attributes the initial attributes
79       */
80      HtmlTextArea(final String qualifiedName, final SgmlPage page,
81              final Map<String, DomAttr> attributes) {
82          super(qualifiedName, page, attributes);
83      }
84  
85      /**
86       * Initializes the default value if necessary. We cannot do it in the constructor
87       * because the child node variable will not have been initialized yet. Must be called
88       * from all methods that use the default value.
89       */
90      private void initDefaultValue() {
91          if (defaultValue_ == null) {
92              defaultValue_ = computeValueFromChildText();
93          }
94      }
95  
96      /**
97       * {@inheritDoc}
98       */
99      @Override
100     public boolean handles(final Event event) {
101         if (event instanceof MouseEvent) {
102             return true;
103         }
104 
105         return super.handles(event);
106     }
107 
108     /**
109      * Returns the value that would be displayed in the text area.
110      * This is the element's "raw value" (spec term). While the dirty value
111      * flag is {@code false}, this is always computed fresh from the current
112      * child text content -- deliberately NOT cached and re-synced via
113      * mutation hooks, since that approach cannot reliably catch every way the
114      * children can change (e.g. a child {@code DomText}'s {@code data} being
115      * reassigned directly bypasses any hook on this element). Once the dirty
116      * flag becomes {@code true} (via {@link #setText(String)} or typing), the
117      * value is held in {@link #rawValue_} and is fully decoupled from the
118      * children until {@link #reset()} clears the flag again.
119      *
120      * @return the text
121      */
122     @Override
123     public final String getText() {
124         if (isValueDirty_) {
125             return rawValue_;
126         }
127         return computeValueFromChildText();
128     }
129 
130     /**
131      * Computes what the raw value would be purely from the current child text
132      * content -- i.e. the spec's "child text content" used both for the initial/
133      * reset raw value and for {@code defaultValue}. Renamed from the old
134      * {@code readValue()}.
135      *
136      * @return the concatenated child text content, with a single leading newline
137      *     stripped per the HTML parsing algorithm
138      */
139     private String computeValueFromChildText() {
140         final StringBuilder builder = new StringBuilder();
141         for (final DomNode node : getChildren()) {
142             if (node instanceof DomText text) {
143                 builder.append(text.getData());
144             }
145         }
146         // if content starts with new line, it is ignored (=> for the parser?)
147         if (builder.length() != 0 && builder.charAt(0) == '\n') {
148             builder.deleteCharAt(0);
149         }
150         return builder.toString();
151     }
152 
153     /**
154      * Sets the new value of this text area. Per spec, this sets the raw value
155      * directly and marks the dirty flag -- it must NOT touch the DOM child
156      * nodes at all, and no subsequent child mutation may affect this value
157      * again until {@link #reset()}.
158      * <p>
159      * Note that this acts like 'pasting' the text, but to simulate characters entry
160      * you should use {@link #type(String)}.
161      * </p>
162      *
163      * @param newValue the new value
164      */
165     @Override
166     public final void setText(final String newValue) {
167         setTextInternal(newValue);
168 
169         HtmlInput.executeOnChangeHandlerIfAppropriate(this);
170     }
171 
172     private void setTextInternal(final String newValue) {
173         final String oldValue = getText();
174 
175         rawValue_ = newValue;
176         isValueDirty_ = true;
177 
178         if (!newValue.equals(oldValue)) {
179             final int pos = newValue.length();
180             setSelectionStart(pos);
181             setSelectionEnd(pos);
182         }
183     }
184 
185     /**
186      * {@inheritDoc}
187      */
188     @Override
189     public NameValuePair[] getSubmitNameValuePairs() {
190         String text = getText();
191         text = text.replace("\r\n", "\n").replace("\n", "\r\n");
192 
193         return new NameValuePair[]{new NameValuePair(getNameAttribute(), text)};
194     }
195 
196     /**
197      * {@inheritDoc}
198      * Per the spec's reset algorithm for textarea elements: clears the dirty
199      * value flag. Once clear, {@link #getText()} automatically recomputes from
200      * the CURRENT child text content (not the page's originally-parsed text,
201      * and not {@link #defaultValue_} -- those can differ from the live
202      * children if the children were mutated while the dirty flag was
203      * {@code true}). Deliberately does not call {@link #setText(String)} /
204      * fire the onchange handler: a form reset fires a {@code reset} event,
205      * not a {@code change} event.
206      * @see SubmittableElement#reset()
207      */
208     @Override
209     public void reset() {
210         final String oldValue = getText();
211 
212         isValueDirty_ = false;
213 
214         final String newValue = computeValueFromChildText();
215         if (!newValue.equals(oldValue)) {
216             final int pos = newValue.length();
217             setSelectionStart(pos);
218             setSelectionEnd(pos);
219         }
220     }
221 
222     /**
223      * {@inheritDoc}
224      * Per spec, {@code defaultValue} is specified in terms of the element's
225      * child text content -- setting it mutates the children (here, via the
226      * same node-replacement helper the old {@code setTextInternal()} used).
227      * Since {@link #getText()} recomputes directly from the current children
228      * whenever the dirty flag is {@code false}, {@code value} is automatically
229      * updated as a side effect ONLY while still clean -- exactly matching
230      * observed real-browser behavior (replacing the old, less precise "if
231      * value still equals old default value" equality check).
232      * @see SubmittableElement#setDefaultValue(String)
233      */
234     @Override
235     public void setDefaultValue(String defaultValue) {
236         initDefaultValue();
237         if (defaultValue == null) {
238             defaultValue = "";
239         }
240 
241         replaceChildTextContent(defaultValue);
242         defaultValue_ = defaultValue;
243     }
244 
245     /**
246      * Replaces this element's child text content with {@code newText}, reusing
247      * an existing text-node child in place where possible rather than always
248      * removing and recreating one (avoids unnecessary DOM node identity churn).
249      * Used only for mutating the DOM representation (e.g. from
250      * {@link #setDefaultValue(String)}) -- NOT for the {@code value}
251      * setter, which must not touch the children at all.
252      *
253      * @param newText the new child text content
254      */
255     private void replaceChildTextContent(final String newText) {
256         DomNode child = getFirstChild();
257         if (child == null) {
258             appendChild(new DomText(getPage(), newText));
259         }
260         else if (child instanceof DomText) {
261             ((DomText) child).setData(newText);
262         }
263         else {
264             DomNode next = child.getNextSibling();
265             while (next != null && !(next instanceof DomText)) {
266                 child = next;
267                 next = child.getNextSibling();
268             }
269 
270             if (next == null) {
271                 removeChild(child);
272                 appendChild(new DomText(getPage(), newText));
273             }
274             else {
275                 ((DomText) next).setData(newText);
276             }
277         }
278     }
279 
280     /**
281      * {@inheritDoc}
282      * @see SubmittableElement#getDefaultValue()
283      */
284     @Override
285     public String getDefaultValue() {
286         initDefaultValue();
287         return defaultValue_;
288     }
289 
290     /**
291      * {@inheritDoc}
292      * This implementation is empty; only check boxes and radio buttons
293      * really care what the default checked value is.
294      * @see SubmittableElement#setDefaultChecked(boolean)
295      * @see HtmlRadioButtonInput#setDefaultChecked(boolean)
296      * @see HtmlCheckBoxInput#setDefaultChecked(boolean)
297      */
298     @Override
299     public void setDefaultChecked(final boolean defaultChecked) {
300         // Empty.
301     }
302 
303     /**
304      * {@inheritDoc} This implementation returns {@code false}; only checkboxes and
305      * radio buttons really care what the default checked value is.
306      * @see SubmittableElement#isDefaultChecked()
307      * @see HtmlRadioButtonInput#isDefaultChecked()
308      * @see HtmlCheckBoxInput#isDefaultChecked()
309      */
310     @Override
311     public boolean isDefaultChecked() {
312         return false;
313     }
314 
315     /**
316      * Returns the value of the attribute {@code name}. Refer to the
317      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
318      * documentation for details on the use of this attribute.
319      *
320      * @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
321      */
322     public final String getNameAttribute() {
323         return getAttributeDirect(DomElement.NAME_ATTRIBUTE);
324     }
325 
326     /**
327      * Returns the value of the attribute {@code rows}. Refer to the
328      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
329      * documentation for details on the use of this attribute.
330      *
331      * @return the value of the attribute {@code rows} or an empty string if that attribute isn't defined
332      */
333     public final String getRowsAttribute() {
334         return getAttributeDirect("rows");
335     }
336 
337     /**
338      * Returns the value of the attribute {@code cols}. 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 cols} or an empty string if that attribute isn't defined
343      */
344     public final String getColumnsAttribute() {
345         return getAttributeDirect("cols");
346     }
347 
348     /**
349      * {@inheritDoc}
350      */
351     @Override
352     public final String getDisabledAttribute() {
353         return getAttributeDirect(ATTRIBUTE_DISABLED);
354     }
355 
356     /**
357      * Returns the value of the attribute {@code readonly}. Refer to the
358      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
359      * documentation for details on the use of this attribute.
360      *
361      * @return the value of the attribute {@code readonly} or an empty string if that attribute isn't defined
362      */
363     public final String getReadOnlyAttribute() {
364         return getAttributeDirect(ATTRIBUTE_READONLY);
365     }
366 
367     /**
368      * Returns the value of the attribute {@code tabindex}. Refer to the
369      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
370      * documentation for details on the use of this attribute.
371      *
372      * @return the value of the attribute {@code tabindex} or an empty string if that attribute isn't defined
373      */
374     public final String getTabIndexAttribute() {
375         return getAttributeDirect("tabindex");
376     }
377 
378     /**
379      * Returns the value of the attribute {@code accesskey}. Refer to the
380      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
381      * documentation for details on the use of this attribute.
382      *
383      * @return the value of the attribute {@code accesskey} or an empty string if that attribute isn't defined
384      */
385     public final String getAccessKeyAttribute() {
386         return getAttributeDirect("accesskey");
387     }
388 
389     /**
390      * Returns the value of the attribute {@code onfocus}. Refer to the
391      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
392      * documentation for details on the use of this attribute.
393      *
394      * @return the value of the attribute {@code onfocus} or an empty string if that attribute isn't defined
395      */
396     public final String getOnFocusAttribute() {
397         return getAttributeDirect("onfocus");
398     }
399 
400     /**
401      * Returns the value of the attribute {@code onblur}. Refer to the
402      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
403      * documentation for details on the use of this attribute.
404      *
405      * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
406      */
407     public final String getOnBlurAttribute() {
408         return getAttributeDirect("onblur");
409     }
410 
411     /**
412      * Returns the value of the attribute {@code onselect}. Refer to the
413      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
414      * documentation for details on the use of this attribute.
415      *
416      * @return the value of the attribute {@code onselect} or an empty string if that attribute isn't defined
417      */
418     public final String getOnSelectAttribute() {
419         return getAttributeDirect("onselect");
420     }
421 
422     /**
423      * Returns the value of the attribute {@code onchange}. 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 onchange} or an empty string if that attribute isn't defined
428      */
429     public final String getOnChangeAttribute() {
430         return getAttributeDirect("onchange");
431     }
432 
433     /**
434      * {@inheritDoc}
435      */
436     @Override
437     public void select() {
438         selectionDelegate_.select();
439     }
440 
441     /**
442      * {@inheritDoc}
443      */
444     @Override
445     public String getSelectedText() {
446         return selectionDelegate_.getSelectedText();
447     }
448 
449     /**
450      * {@inheritDoc}
451      */
452     @Override
453     public int getSelectionStart() {
454         return selectionDelegate_.getSelectionStart();
455     }
456 
457     /**
458      * {@inheritDoc}
459      */
460     @Override
461     public void setSelectionStart(final int selectionStart) {
462         selectionDelegate_.setSelectionStart(selectionStart);
463     }
464 
465     /**
466      * {@inheritDoc}
467      */
468     @Override
469     public int getSelectionEnd() {
470         return selectionDelegate_.getSelectionEnd();
471     }
472 
473     /**
474      * {@inheritDoc}
475      */
476     @Override
477     public void setSelectionEnd(final int selectionEnd) {
478         selectionDelegate_.setSelectionEnd(selectionEnd);
479     }
480 
481     /**
482      * {@inheritDoc}
483      */
484     @Override
485     protected boolean printXml(final String indent, final boolean indentBefore, final PrintWriter printWriter) {
486         printWriter.print(indent + "<");
487         printOpeningTagContentAsXml(printWriter);
488 
489         printWriter.print(">");
490         printWriter.print(StringUtils.escapeXml(getText()));
491         printWriter.print("</textarea>");
492         return true;
493     }
494 
495     /**
496      * {@inheritDoc}
497      */
498     @Override
499     protected void doType(final char c, final boolean lastType) {
500         doTypeProcessor_.doType(getText(), selectionDelegate_, c, this, lastType);
501     }
502 
503     /**
504      * {@inheritDoc}
505      */
506     @Override
507     protected void doType(final int keyCode, final boolean lastType) {
508         doTypeProcessor_.doType(getText(), selectionDelegate_, keyCode, this, lastType);
509     }
510 
511     /**
512      * {@inheritDoc}
513      */
514     @Override
515     protected void typeDone(final String newValue, final boolean notifyAttributeChangeListeners) {
516         setTextInternal(newValue);
517     }
518 
519     /**
520      * {@inheritDoc}
521      */
522     @Override
523     protected boolean acceptChar(final char c) {
524         return super.acceptChar(c) || c == '\n' || c == '\r';
525     }
526 
527     /**
528      * {@inheritDoc}
529      */
530     @Override
531     public void focus() {
532         super.focus();
533         valueAtFocus_ = getText();
534     }
535 
536     /**
537      * {@inheritDoc}
538      */
539     @Override
540     public void removeFocus() {
541         super.removeFocus();
542         if (valueAtFocus_ != null && !valueAtFocus_.equals(getText())) {
543             HtmlInput.executeOnChangeHandlerIfAppropriate(this);
544         }
545         valueAtFocus_ = null;
546     }
547 
548     /**
549      * Sets the {@code readOnly} attribute.
550      *
551      * @param isReadOnly {@code true} if this element is read only
552      */
553     public void setReadOnly(final boolean isReadOnly) {
554         if (isReadOnly) {
555             setAttribute(ATTRIBUTE_READONLY, "");
556         }
557         else {
558             removeAttribute(ATTRIBUTE_READONLY);
559         }
560     }
561 
562     /**
563      * Returns {@code true} if this element is read only.
564      * @return {@code true} if this element is read only
565      */
566     public boolean isReadOnly() {
567         return hasAttribute(ATTRIBUTE_READONLY);
568     }
569 
570     /**
571      * {@inheritDoc}
572      * @return {@code true} to make generated XML readable as HTML
573      */
574     @Override
575     protected boolean isEmptyXmlTagExpanded() {
576         return true;
577     }
578 
579     /**
580      * {@inheritDoc}
581      */
582     @Override
583     public DisplayStyle getDefaultStyleDisplay() {
584         return DisplayStyle.INLINE_BLOCK;
585     }
586 
587     /**
588      * Returns the value of the {@code placeholder} attribute.
589      *
590      * @return the value of the {@code placeholder} attribute
591      */
592     public String getPlaceholder() {
593         return getAttributeDirect("placeholder");
594     }
595 
596     /**
597      * Sets the {@code placeholder} attribute.
598      *
599      * @param placeholder the {@code placeholder} attribute
600      */
601     public void setPlaceholder(final String placeholder) {
602         setAttribute("placeholder", placeholder);
603     }
604 
605     /**
606      * {@inheritDoc}
607      */
608     @Override
609     protected boolean isRequiredSupported() {
610         return true;
611     }
612 
613     /**
614      * {@inheritDoc}
615      */
616     @Override
617     public DomNode cloneNode(final boolean deep) {
618         final HtmlTextArea newnode = (HtmlTextArea) super.cloneNode(deep);
619         newnode.selectionDelegate_ = new SelectableTextSelectionDelegate(newnode);
620         newnode.doTypeProcessor_ = new DoTypeProcessor(newnode);
621 
622         return newnode;
623     }
624 
625     /**
626      * {@inheritDoc}
627      */
628     @Override
629     public boolean willValidate() {
630         return !isDisabled() && !isReadOnly();
631     }
632 
633     /**
634      * {@inheritDoc}
635      */
636     @Override
637     public String getCustomValidity() {
638         return customValidity_;
639     }
640 
641     /**
642      * {@inheritDoc}
643      */
644     @Override
645     public void setCustomValidity(final String message) {
646         customValidity_ = message;
647     }
648 
649     /**
650      * {@inheritDoc}
651      */
652     @Override
653     public boolean isValid() {
654         return isValidValidityState();
655     }
656 
657     /**
658      * {@inheritDoc}
659      */
660     @Override
661     public boolean isCustomErrorValidityState() {
662         return !StringUtils.isEmptyOrNull(customValidity_);
663     }
664 
665     @Override
666     public boolean isValidValidityState() {
667         return !isCustomErrorValidityState()
668                 && !isValueMissingValidityState();
669     }
670 
671     /**
672      * {@inheritDoc}
673      */
674     @Override
675     public boolean isValueMissingValidityState() {
676         return ATTRIBUTE_NOT_DEFINED != getAttributeDirect(ATTRIBUTE_REQUIRED)
677                 && getText().isEmpty();
678     }
679 }