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 }