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 }