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 (a line feed directly after
137      *     the start tag is already removed by the HTML parser)
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         return builder.toString();
147     }
148 
149     /**
150      * Sets the new value of this text area. Per spec, this sets the raw value
151      * directly and marks the dirty flag -- it must NOT touch the DOM child
152      * nodes at all, and no subsequent child mutation may affect this value
153      * again until {@link #reset()}.
154      * <p>
155      * Note that this acts like 'pasting' the text, but to simulate characters entry
156      * you should use {@link #type(String)}.
157      * </p>
158      *
159      * @param newValue the new value
160      */
161     @Override
162     public final void setText(final String newValue) {
163         setTextInternal(newValue);
164 
165         HtmlInput.executeOnChangeHandlerIfAppropriate(this);
166     }
167 
168     private void setTextInternal(final String newValue) {
169         final String oldValue = getText();
170 
171         rawValue_ = newValue;
172         isValueDirty_ = true;
173 
174         if (!newValue.equals(oldValue)) {
175             final int pos = newValue.length();
176             setSelectionStart(pos);
177             setSelectionEnd(pos);
178         }
179     }
180 
181     /**
182      * {@inheritDoc}
183      */
184     @Override
185     public NameValuePair[] getSubmitNameValuePairs() {
186         String text = getText();
187         text = text.replace("\r\n", "\n").replace("\n", "\r\n");
188 
189         return new NameValuePair[]{new NameValuePair(getNameAttribute(), text)};
190     }
191 
192     /**
193      * {@inheritDoc}
194      * Per the spec's reset algorithm for textarea elements: clears the dirty
195      * value flag. Once clear, {@link #getText()} automatically recomputes from
196      * the CURRENT child text content (not the page's originally-parsed text,
197      * and not {@link #defaultValue_} -- those can differ from the live
198      * children if the children were mutated while the dirty flag was
199      * {@code true}). Deliberately does not call {@link #setText(String)} /
200      * fire the onchange handler: a form reset fires a {@code reset} event,
201      * not a {@code change} event.
202      * @see SubmittableElement#reset()
203      */
204     @Override
205     public void reset() {
206         final String oldValue = getText();
207 
208         isValueDirty_ = false;
209 
210         final String newValue = computeValueFromChildText();
211         if (!newValue.equals(oldValue)) {
212             final int pos = newValue.length();
213             setSelectionStart(pos);
214             setSelectionEnd(pos);
215         }
216     }
217 
218     /**
219      * {@inheritDoc}
220      * Per spec, {@code defaultValue} is specified in terms of the element's
221      * child text content -- setting it mutates the children (here, via the
222      * same node-replacement helper the old {@code setTextInternal()} used).
223      * Since {@link #getText()} recomputes directly from the current children
224      * whenever the dirty flag is {@code false}, {@code value} is automatically
225      * updated as a side effect ONLY while still clean -- exactly matching
226      * observed real-browser behavior (replacing the old, less precise "if
227      * value still equals old default value" equality check).
228      * @see SubmittableElement#setDefaultValue(String)
229      */
230     @Override
231     public void setDefaultValue(String defaultValue) {
232         initDefaultValue();
233         if (defaultValue == null) {
234             defaultValue = "";
235         }
236 
237         replaceChildTextContent(defaultValue);
238         defaultValue_ = defaultValue;
239     }
240 
241     /**
242      * Replaces this element's child text content with {@code newText}, reusing
243      * an existing text-node child in place where possible rather than always
244      * removing and recreating one (avoids unnecessary DOM node identity churn).
245      * Used only for mutating the DOM representation (e.g. from
246      * {@link #setDefaultValue(String)}) -- NOT for the {@code value}
247      * setter, which must not touch the children at all.
248      *
249      * @param newText the new child text content
250      */
251     private void replaceChildTextContent(final String newText) {
252         DomNode child = getFirstChild();
253         if (child == null) {
254             appendChild(new DomText(getPage(), newText));
255         }
256         else if (child instanceof DomText) {
257             ((DomText) child).setData(newText);
258         }
259         else {
260             DomNode next = child.getNextSibling();
261             while (next != null && !(next instanceof DomText)) {
262                 child = next;
263                 next = child.getNextSibling();
264             }
265 
266             if (next == null) {
267                 removeChild(child);
268                 appendChild(new DomText(getPage(), newText));
269             }
270             else {
271                 ((DomText) next).setData(newText);
272             }
273         }
274     }
275 
276     /**
277      * {@inheritDoc}
278      * @see SubmittableElement#getDefaultValue()
279      */
280     @Override
281     public String getDefaultValue() {
282         initDefaultValue();
283         return defaultValue_;
284     }
285 
286     /**
287      * {@inheritDoc}
288      * This implementation is empty; only check boxes and radio buttons
289      * really care what the default checked value is.
290      * @see SubmittableElement#setDefaultChecked(boolean)
291      * @see HtmlRadioButtonInput#setDefaultChecked(boolean)
292      * @see HtmlCheckBoxInput#setDefaultChecked(boolean)
293      */
294     @Override
295     public void setDefaultChecked(final boolean defaultChecked) {
296         // Empty.
297     }
298 
299     /**
300      * {@inheritDoc} This implementation returns {@code false}; only checkboxes and
301      * radio buttons really care what the default checked value is.
302      * @see SubmittableElement#isDefaultChecked()
303      * @see HtmlRadioButtonInput#isDefaultChecked()
304      * @see HtmlCheckBoxInput#isDefaultChecked()
305      */
306     @Override
307     public boolean isDefaultChecked() {
308         return false;
309     }
310 
311     /**
312      * Returns the value of the attribute {@code name}. Refer to the
313      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
314      * documentation for details on the use of this attribute.
315      *
316      * @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
317      */
318     public final String getNameAttribute() {
319         return getAttributeDirect(DomElement.NAME_ATTRIBUTE);
320     }
321 
322     /**
323      * Returns the value of the attribute {@code rows}. Refer to the
324      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
325      * documentation for details on the use of this attribute.
326      *
327      * @return the value of the attribute {@code rows} or an empty string if that attribute isn't defined
328      */
329     public final String getRowsAttribute() {
330         return getAttributeDirect("rows");
331     }
332 
333     /**
334      * Returns the value of the attribute {@code cols}. Refer to the
335      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
336      * documentation for details on the use of this attribute.
337      *
338      * @return the value of the attribute {@code cols} or an empty string if that attribute isn't defined
339      */
340     public final String getColumnsAttribute() {
341         return getAttributeDirect("cols");
342     }
343 
344     /**
345      * {@inheritDoc}
346      */
347     @Override
348     public final String getDisabledAttribute() {
349         return getAttributeDirect(ATTRIBUTE_DISABLED);
350     }
351 
352     /**
353      * Returns the value of the attribute {@code readonly}. Refer to the
354      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
355      * documentation for details on the use of this attribute.
356      *
357      * @return the value of the attribute {@code readonly} or an empty string if that attribute isn't defined
358      */
359     public final String getReadOnlyAttribute() {
360         return getAttributeDirect(ATTRIBUTE_READONLY);
361     }
362 
363     /**
364      * Returns the value of the attribute {@code tabindex}. Refer to the
365      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
366      * documentation for details on the use of this attribute.
367      *
368      * @return the value of the attribute {@code tabindex} or an empty string if that attribute isn't defined
369      */
370     public final String getTabIndexAttribute() {
371         return getAttributeDirect("tabindex");
372     }
373 
374     /**
375      * Returns the value of the attribute {@code accesskey}. 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 accesskey} 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} or an empty string if that attribute isn't defined
391      */
392     public final String getOnFocusAttribute() {
393         return getAttributeDirect("onfocus");
394     }
395 
396     /**
397      * Returns the value of the attribute {@code onblur}. Refer to the
398      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
399      * documentation for details on the use of this attribute.
400      *
401      * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
402      */
403     public final String getOnBlurAttribute() {
404         return getAttributeDirect("onblur");
405     }
406 
407     /**
408      * Returns the value of the attribute {@code onselect}. Refer to the
409      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
410      * documentation for details on the use of this attribute.
411      *
412      * @return the value of the attribute {@code onselect} or an empty string if that attribute isn't defined
413      */
414     public final String getOnSelectAttribute() {
415         return getAttributeDirect("onselect");
416     }
417 
418     /**
419      * Returns the value of the attribute {@code onchange}. Refer to the
420      * <a href="http://www.w3.org/TR/html401/">HTML 4.01</a>
421      * documentation for details on the use of this attribute.
422      *
423      * @return the value of the attribute {@code onchange} or an empty string if that attribute isn't defined
424      */
425     public final String getOnChangeAttribute() {
426         return getAttributeDirect("onchange");
427     }
428 
429     /**
430      * {@inheritDoc}
431      */
432     @Override
433     public void select() {
434         selectionDelegate_.select();
435     }
436 
437     /**
438      * {@inheritDoc}
439      */
440     @Override
441     public String getSelectedText() {
442         return selectionDelegate_.getSelectedText();
443     }
444 
445     /**
446      * {@inheritDoc}
447      */
448     @Override
449     public int getSelectionStart() {
450         return selectionDelegate_.getSelectionStart();
451     }
452 
453     /**
454      * {@inheritDoc}
455      */
456     @Override
457     public void setSelectionStart(final int selectionStart) {
458         selectionDelegate_.setSelectionStart(selectionStart);
459     }
460 
461     /**
462      * {@inheritDoc}
463      */
464     @Override
465     public int getSelectionEnd() {
466         return selectionDelegate_.getSelectionEnd();
467     }
468 
469     /**
470      * {@inheritDoc}
471      */
472     @Override
473     public void setSelectionEnd(final int selectionEnd) {
474         selectionDelegate_.setSelectionEnd(selectionEnd);
475     }
476 
477     /**
478      * {@inheritDoc}
479      */
480     @Override
481     protected boolean printXml(final String indent, final boolean indentBefore, final PrintWriter printWriter) {
482         printWriter.print(indent + "<");
483         printOpeningTagContentAsXml(printWriter);
484 
485         printWriter.print(">");
486         printWriter.print(StringUtils.escapeXml(getText()));
487         printWriter.print("</textarea>");
488         return true;
489     }
490 
491     /**
492      * {@inheritDoc}
493      */
494     @Override
495     protected void doType(final char c, final boolean lastType) {
496         doTypeProcessor_.doType(getText(), selectionDelegate_, c, this, lastType);
497     }
498 
499     /**
500      * {@inheritDoc}
501      */
502     @Override
503     protected void doType(final int keyCode, final boolean lastType) {
504         doTypeProcessor_.doType(getText(), selectionDelegate_, keyCode, this, lastType);
505     }
506 
507     /**
508      * {@inheritDoc}
509      */
510     @Override
511     protected void typeDone(final String newValue, final boolean notifyAttributeChangeListeners) {
512         setTextInternal(newValue);
513     }
514 
515     /**
516      * {@inheritDoc}
517      */
518     @Override
519     protected boolean acceptChar(final char c) {
520         return super.acceptChar(c) || c == '\n' || c == '\r';
521     }
522 
523     /**
524      * {@inheritDoc}
525      */
526     @Override
527     public void focus() {
528         super.focus();
529         valueAtFocus_ = getText();
530     }
531 
532     /**
533      * {@inheritDoc}
534      */
535     @Override
536     public void removeFocus() {
537         super.removeFocus();
538         if (valueAtFocus_ != null && !valueAtFocus_.equals(getText())) {
539             HtmlInput.executeOnChangeHandlerIfAppropriate(this);
540         }
541         valueAtFocus_ = null;
542     }
543 
544     /**
545      * Sets the {@code readOnly} attribute.
546      *
547      * @param isReadOnly {@code true} if this element is read only
548      */
549     public void setReadOnly(final boolean isReadOnly) {
550         if (isReadOnly) {
551             setAttribute(ATTRIBUTE_READONLY, "");
552         }
553         else {
554             removeAttribute(ATTRIBUTE_READONLY);
555         }
556     }
557 
558     /**
559      * Returns {@code true} if this element is read only.
560      * @return {@code true} if this element is read only
561      */
562     public boolean isReadOnly() {
563         return hasAttribute(ATTRIBUTE_READONLY);
564     }
565 
566     /**
567      * {@inheritDoc}
568      * @return {@code true} to make generated XML readable as HTML
569      */
570     @Override
571     protected boolean isEmptyXmlTagExpanded() {
572         return true;
573     }
574 
575     /**
576      * {@inheritDoc}
577      */
578     @Override
579     public DisplayStyle getDefaultStyleDisplay() {
580         return DisplayStyle.INLINE_BLOCK;
581     }
582 
583     /**
584      * Returns the value of the {@code placeholder} attribute.
585      *
586      * @return the value of the {@code placeholder} attribute
587      */
588     public String getPlaceholder() {
589         return getAttributeDirect("placeholder");
590     }
591 
592     /**
593      * Sets the {@code placeholder} attribute.
594      *
595      * @param placeholder the {@code placeholder} attribute
596      */
597     public void setPlaceholder(final String placeholder) {
598         setAttribute("placeholder", placeholder);
599     }
600 
601     /**
602      * {@inheritDoc}
603      */
604     @Override
605     protected boolean isRequiredSupported() {
606         return true;
607     }
608 
609     /**
610      * {@inheritDoc}
611      */
612     @Override
613     public DomNode cloneNode(final boolean deep) {
614         final HtmlTextArea newnode = (HtmlTextArea) super.cloneNode(deep);
615         newnode.selectionDelegate_ = new SelectableTextSelectionDelegate(newnode);
616         newnode.doTypeProcessor_ = new DoTypeProcessor(newnode);
617 
618         return newnode;
619     }
620 
621     /**
622      * {@inheritDoc}
623      */
624     @Override
625     public boolean willValidate() {
626         return !isDisabled() && !isReadOnly();
627     }
628 
629     /**
630      * {@inheritDoc}
631      */
632     @Override
633     public String getCustomValidity() {
634         return customValidity_;
635     }
636 
637     /**
638      * {@inheritDoc}
639      */
640     @Override
641     public void setCustomValidity(final String message) {
642         customValidity_ = message;
643     }
644 
645     /**
646      * {@inheritDoc}
647      */
648     @Override
649     public boolean isValid() {
650         return isValidValidityState();
651     }
652 
653     /**
654      * {@inheritDoc}
655      */
656     @Override
657     public boolean isCustomErrorValidityState() {
658         return !StringUtils.isEmptyOrNull(customValidity_);
659     }
660 
661     @Override
662     public boolean isValidValidityState() {
663         return !isCustomErrorValidityState()
664                 && !isValueMissingValidityState();
665     }
666 
667     /**
668      * {@inheritDoc}
669      */
670     @Override
671     public boolean isValueMissingValidityState() {
672         return ATTRIBUTE_NOT_DEFINED != getAttributeDirect(ATTRIBUTE_REQUIRED)
673                 && getText().isEmpty();
674     }
675 }