View Javadoc
1   /*
2    * Copyright (c) 2002-2017 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    * http://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 com.gargoylesoftware.htmlunit.html;
16  
17  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.CSS_DISPLAY_BLOCK;
18  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EVENT_MOUSE_ON_DISABLED;
19  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLTEXTAREA_SET_DEFAULT_VALUE_UPDATES_VALUE;
20  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLTEXTAREA_USE_ALL_TEXT_CHILDREN;
21  import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_INPUT_SET_VALUE_MOVE_SELECTION_TO_START;
22  
23  import java.io.PrintWriter;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashSet;
27  import java.util.Map;
28  
29  import org.apache.commons.lang3.StringEscapeUtils;
30  
31  import com.gargoylesoftware.htmlunit.SgmlPage;
32  import com.gargoylesoftware.htmlunit.html.impl.SelectableTextInput;
33  import com.gargoylesoftware.htmlunit.html.impl.SelectableTextSelectionDelegate;
34  import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
35  import com.gargoylesoftware.htmlunit.javascript.host.event.MouseEvent;
36  import com.gargoylesoftware.htmlunit.util.NameValuePair;
37  
38  /**
39   * Wrapper for the HTML element "textarea".
40   *
41   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
42   * @author <a href="mailto:BarnabyCourt@users.sourceforge.net">Barnaby Court</a>
43   * @author David K. Taylor
44   * @author <a href="mailto:cse@dynabean.de">Christian Sell</a>
45   * @author David D. Kilzer
46   * @author Marc Guillemot
47   * @author Daniel Gredler
48   * @author Ahmed Ashour
49   * @author Sudhan Moghe
50   * @author Amit Khanna
51   * @author Ronald Brill
52   * @author Frank Danek
53   */
54  public class HtmlTextArea extends HtmlElement implements DisabledElement, SubmittableElement, SelectableTextInput,
55      FormFieldWithNameHistory {
56      /** The HTML tag represented by this element. */
57      public static final String TAG_NAME = "textarea";
58  
59      private String defaultValue_;
60      private String valueAtFocus_;
61      private String originalName_;
62      private Collection<String> newNames_ = Collections.emptySet();
63  
64      private final SelectableTextSelectionDelegate selectionDelegate_ = new SelectableTextSelectionDelegate(this);
65  
66      private final DoTypeProcessor doTypeProcessor_ = new DoTypeProcessor(this);
67  
68      /**
69       * Creates an instance.
70       *
71       * @param qualifiedName the qualified name of the element type to instantiate
72       * @param page the page that contains this element
73       * @param attributes the initial attributes
74       */
75      HtmlTextArea(final String qualifiedName, final SgmlPage page,
76              final Map<String, DomAttr> attributes) {
77          super(qualifiedName, page, attributes);
78          originalName_ = getNameAttribute();
79      }
80  
81      /**
82       * Initializes the default value if necessary. We cannot do it in the constructor
83       * because the child node variable will not have been initialized yet. Must be called
84       * from all methods that use the default value.
85       */
86      private void initDefaultValue() {
87          if (defaultValue_ == null) {
88              defaultValue_ = readValue();
89          }
90      }
91  
92      /**
93       * {@inheritDoc}
94       */
95      @Override
96      public boolean handles(final Event event) {
97          if (event instanceof MouseEvent && hasFeature(EVENT_MOUSE_ON_DISABLED)) {
98              return true;
99          }
100 
101         return super.handles(event);
102     }
103 
104     /**
105      * Returns the value that would be displayed in the text area.
106      *
107      * @return the text
108      */
109     @Override
110     public final String getText() {
111         if (hasFeature(HTMLTEXTAREA_USE_ALL_TEXT_CHILDREN)) {
112             return readValueIE();
113         }
114         return readValue();
115     }
116 
117     private String readValue() {
118         final StringBuilder builder = new StringBuilder();
119         for (final DomNode node : getChildren()) {
120             if (node instanceof DomText) {
121                 builder.append(((DomText) node).getData());
122             }
123         }
124         // if content starts with new line, it is ignored (=> for the parser?)
125         if (builder.length() != 0 && builder.charAt(0) == '\n') {
126             builder.deleteCharAt(0);
127         }
128         return builder.toString();
129     }
130 
131     private String readValueIE() {
132         final StringBuilder builder = new StringBuilder();
133         for (final DomNode node : getDescendants()) {
134             if (node instanceof DomText) {
135                 builder.append(((DomText) node).getData());
136             }
137         }
138         // if content starts with new line, it is ignored (=> for the parser?)
139         if (builder.length() != 0 && builder.charAt(0) == '\n') {
140             builder.deleteCharAt(0);
141         }
142         return builder.toString();
143     }
144 
145     /**
146      * Sets the new value of this text area.
147      *
148      * Note that this acts like 'pasting' the text, but to simulate characters entry
149      * you should use {@link #type(String)}.
150      *
151      * @param newValue the new value
152      */
153     @Override
154     public final void setText(final String newValue) {
155         setTextInternal(newValue);
156 
157         HtmlInput.executeOnChangeHandlerIfAppropriate(this);
158     }
159 
160     private void setTextInternal(final String newValue) {
161         initDefaultValue();
162         DomNode child = getFirstChild();
163         if (child == null) {
164             final DomText newChild = new DomText(getPage(), newValue);
165             appendChild(newChild);
166         }
167         else {
168             if (hasFeature(HTMLTEXTAREA_USE_ALL_TEXT_CHILDREN)) {
169                 removeAllChildren();
170                 final DomText newChild = new DomText(getPage(), newValue);
171                 appendChild(newChild);
172             }
173             else {
174                 DomNode next = child.getNextSibling();
175                 while (next != null && !(next instanceof DomText)) {
176                     child = next;
177                     next = child.getNextSibling();
178                 }
179 
180                 if (next == null) {
181                     removeChild(child);
182                     final DomText newChild = new DomText(getPage(), newValue);
183                     appendChild(newChild);
184                 }
185                 else {
186                     ((DomText) next).setData(newValue);
187                 }
188             }
189         }
190 
191         int pos = 0;
192         if (!hasFeature(JS_INPUT_SET_VALUE_MOVE_SELECTION_TO_START)) {
193             pos = newValue.length();
194         }
195         setSelectionStart(pos);
196         setSelectionEnd(pos);
197     }
198 
199     /**
200      * {@inheritDoc}
201      */
202     @Override
203     public NameValuePair[] getSubmitNameValuePairs() {
204         String text = getText();
205         text = text.replace("\r\n", "\n").replace("\n", "\r\n");
206 
207         return new NameValuePair[]{new NameValuePair(getNameAttribute(), text)};
208     }
209 
210     /**
211      * {@inheritDoc}
212      * @see SubmittableElement#reset()
213      */
214     @Override
215     public void reset() {
216         initDefaultValue();
217         setText(defaultValue_);
218     }
219 
220     /**
221      * {@inheritDoc}
222      * @see SubmittableElement#setDefaultValue(String)
223      */
224     @Override
225     public void setDefaultValue(String defaultValue) {
226         initDefaultValue();
227         if (defaultValue == null) {
228             defaultValue = "";
229         }
230 
231         // for FF, if value is still default value, change value too
232         if (hasFeature(HTMLTEXTAREA_SET_DEFAULT_VALUE_UPDATES_VALUE)
233                 && getText().equals(getDefaultValue())) {
234             setTextInternal(defaultValue);
235         }
236         defaultValue_ = defaultValue;
237     }
238 
239     /**
240      * {@inheritDoc}
241      * @see SubmittableElement#getDefaultValue()
242      */
243     @Override
244     public String getDefaultValue() {
245         initDefaultValue();
246         return defaultValue_;
247     }
248 
249     /**
250      * {@inheritDoc} This implementation is empty; only checkboxes and radio buttons
251      * really care what the default checked value is.
252      * @see SubmittableElement#setDefaultChecked(boolean)
253      * @see HtmlRadioButtonInput#setDefaultChecked(boolean)
254      * @see HtmlCheckBoxInput#setDefaultChecked(boolean)
255      */
256     @Override
257     public void setDefaultChecked(final boolean defaultChecked) {
258         // Empty.
259     }
260 
261     /**
262      * {@inheritDoc} This implementation returns {@code false}; only checkboxes and
263      * radio buttons really care what the default checked value is.
264      * @see SubmittableElement#isDefaultChecked()
265      * @see HtmlRadioButtonInput#isDefaultChecked()
266      * @see HtmlCheckBoxInput#isDefaultChecked()
267      */
268     @Override
269     public boolean isDefaultChecked() {
270         return false;
271     }
272 
273     /**
274      * Returns the value of the attribute {@code name}. Refer to the
275      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
276      * documentation for details on the use of this attribute.
277      *
278      * @return the value of the attribute {@code name} or an empty string if that attribute isn't defined
279      */
280     public final String getNameAttribute() {
281         return getAttribute("name");
282     }
283 
284     /**
285      * Returns the value of the attribute {@code rows}. Refer to the
286      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
287      * documentation for details on the use of this attribute.
288      *
289      * @return the value of the attribute {@code rows} or an empty string if that attribute isn't defined
290      */
291     public final String getRowsAttribute() {
292         return getAttribute("rows");
293     }
294 
295     /**
296      * Returns the value of the attribute {@code cols}. Refer to the
297      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
298      * documentation for details on the use of this attribute.
299      *
300      * @return the value of the attribute {@code cols} or an empty string if that attribute isn't defined
301      */
302     public final String getColumnsAttribute() {
303         return getAttribute("cols");
304     }
305 
306     /**
307      * {@inheritDoc}
308      */
309     @Override
310     public final boolean isDisabled() {
311         return hasAttribute("disabled");
312     }
313 
314     /**
315      * {@inheritDoc}
316      */
317     @Override
318     public final String getDisabledAttribute() {
319         return getAttribute("disabled");
320     }
321 
322     /**
323      * Returns the value of the attribute {@code readonly}. 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 readonly} or an empty string if that attribute isn't defined
328      */
329     public final String getReadOnlyAttribute() {
330         return getAttribute("readonly");
331     }
332 
333     /**
334      * Returns the value of the attribute {@code tabindex}. 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 tabindex} or an empty string if that attribute isn't defined
339      */
340     public final String getTabIndexAttribute() {
341         return getAttribute("tabindex");
342     }
343 
344     /**
345      * Returns the value of the attribute {@code accesskey}. Refer to the
346      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
347      * documentation for details on the use of this attribute.
348      *
349      * @return the value of the attribute {@code accesskey} or an empty string if that attribute isn't defined
350      */
351     public final String getAccessKeyAttribute() {
352         return getAttribute("accesskey");
353     }
354 
355     /**
356      * Returns the value of the attribute {@code onfocus}. Refer to the
357      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
358      * documentation for details on the use of this attribute.
359      *
360      * @return the value of the attribute {@code onfocus} or an empty string if that attribute isn't defined
361      */
362     public final String getOnFocusAttribute() {
363         return getAttribute("onfocus");
364     }
365 
366     /**
367      * Returns the value of the attribute {@code onblur}. Refer to the
368      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
369      * documentation for details on the use of this attribute.
370      *
371      * @return the value of the attribute {@code onblur} or an empty string if that attribute isn't defined
372      */
373     public final String getOnBlurAttribute() {
374         return getAttribute("onblur");
375     }
376 
377     /**
378      * Returns the value of the attribute {@code onselect}. Refer to the
379      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
380      * documentation for details on the use of this attribute.
381      *
382      * @return the value of the attribute {@code onselect} or an empty string if that attribute isn't defined
383      */
384     public final String getOnSelectAttribute() {
385         return getAttribute("onselect");
386     }
387 
388     /**
389      * Returns the value of the attribute {@code onchange}. Refer to the
390      * <a href='http://www.w3.org/TR/html401/'>HTML 4.01</a>
391      * documentation for details on the use of this attribute.
392      *
393      * @return the value of the attribute {@code onchange} or an empty string if that attribute isn't defined
394      */
395     public final String getOnChangeAttribute() {
396         return getAttribute("onchange");
397     }
398 
399     /**
400      * {@inheritDoc}
401      */
402     @Override
403     public void select() {
404         selectionDelegate_.select();
405     }
406 
407     /**
408      * {@inheritDoc}
409      */
410     @Override
411     public String getSelectedText() {
412         return selectionDelegate_.getSelectedText();
413     }
414 
415     /**
416      * {@inheritDoc}
417      */
418     @Override
419     public int getSelectionStart() {
420         return selectionDelegate_.getSelectionStart();
421     }
422 
423     /**
424      * {@inheritDoc}
425      */
426     @Override
427     public void setSelectionStart(final int selectionStart) {
428         selectionDelegate_.setSelectionStart(selectionStart);
429     }
430 
431     /**
432      * {@inheritDoc}
433      */
434     @Override
435     public int getSelectionEnd() {
436         return selectionDelegate_.getSelectionEnd();
437     }
438 
439     /**
440      * {@inheritDoc}
441      */
442     @Override
443     public void setSelectionEnd(final int selectionEnd) {
444         selectionDelegate_.setSelectionEnd(selectionEnd);
445     }
446 
447     /**
448      * Recursively write the XML data for the node tree starting at <code>node</code>.
449      *
450      * @param indent white space to indent child nodes
451      * @param printWriter writer where child nodes are written
452      */
453     @Override
454     protected void printXml(final String indent, final PrintWriter printWriter) {
455         printWriter.print(indent + "<");
456         printOpeningTagContentAsXml(printWriter);
457 
458         printWriter.print(">");
459         printWriter.print(StringEscapeUtils.escapeXml10(getText()));
460         printWriter.print("</textarea>");
461     }
462 
463     /**
464      * {@inheritDoc}
465      */
466     @Override
467     protected void doType(final char c, final boolean startAtEnd, final boolean lastType) {
468         if (startAtEnd) {
469             selectionDelegate_.setSelectionStart(getText().length());
470         }
471         doTypeProcessor_.doType(getText(), selectionDelegate_, c, this, lastType);
472     }
473 
474     /**
475      * {@inheritDoc}
476      */
477     @Override
478     protected void doType(final int keyCode, final boolean startAtEnd, final boolean lastType) {
479         if (startAtEnd) {
480             selectionDelegate_.setSelectionStart(getText().length());
481         }
482         doTypeProcessor_.doType(getText(), selectionDelegate_, keyCode, this, lastType);
483     }
484 
485     /**
486      * {@inheritDoc}
487      */
488     @Override
489     protected void typeDone(final String newValue, final boolean notifyAttributeChangeListeners) {
490         setTextInternal(newValue);
491     }
492 
493     /**
494      * {@inheritDoc}
495      */
496     @Override
497     protected boolean acceptChar(final char c) {
498         return super.acceptChar(c) || c == '\n' || c == '\r';
499     }
500 
501     /**
502      * {@inheritDoc}
503      */
504     @Override
505     public void focus() {
506         super.focus();
507         valueAtFocus_ = getText();
508     }
509 
510     /**
511      * {@inheritDoc}
512      */
513     @Override
514     public void removeFocus() {
515         super.removeFocus();
516         if (!valueAtFocus_.equals(getText())) {
517             HtmlInput.executeOnChangeHandlerIfAppropriate(this);
518         }
519         valueAtFocus_ = null;
520     }
521 
522     /**
523      * Sets the {@code readOnly} attribute.
524      *
525      * @param isReadOnly {@code true} if this element is read only
526      */
527     public void setReadOnly(final boolean isReadOnly) {
528         if (isReadOnly) {
529             setAttribute("readOnly", "readOnly");
530         }
531         else {
532             removeAttribute("readOnly");
533         }
534     }
535 
536     /**
537      * Returns {@code true} if this element is read only.
538      * @return {@code true} if this element is read only
539      */
540     public boolean isReadOnly() {
541         return hasAttribute("readOnly");
542     }
543 
544     /**
545      * {@inheritDoc}
546      */
547     @Override
548     protected Object clone() throws CloneNotSupportedException {
549         return new HtmlTextArea(getQualifiedName(), getPage(), getAttributesMap());
550     }
551 
552     /**
553      * {@inheritDoc}
554      */
555     @Override
556     protected void setAttributeNS(final String namespaceURI, final String qualifiedName, final String attributeValue,
557             final boolean notifyAttributeChangeListeners, final boolean notifyMutationObservers) {
558         if ("name".equals(qualifiedName)) {
559             if (newNames_.isEmpty()) {
560                 newNames_ = new HashSet<>();
561             }
562             newNames_.add(attributeValue);
563         }
564         super.setAttributeNS(namespaceURI, qualifiedName, attributeValue, notifyAttributeChangeListeners,
565                 notifyMutationObservers);
566     }
567 
568     /**
569      * {@inheritDoc}
570      */
571     @Override
572     public String getOriginalName() {
573         return originalName_;
574     }
575 
576     /**
577      * {@inheritDoc}
578      */
579     @Override
580     public Collection<String> getNewNames() {
581         return newNames_;
582     }
583 
584     /**
585      * {@inheritDoc}
586      * @return {@code true} to make generated XML readable as HTML
587      */
588     @Override
589     protected boolean isEmptyXmlTagExpanded() {
590         return true;
591     }
592 
593     /**
594      * {@inheritDoc}
595      */
596     @Override
597     public DisplayStyle getDefaultStyleDisplay() {
598         if (hasFeature(CSS_DISPLAY_BLOCK)) {
599             return DisplayStyle.INLINE;
600         }
601         return DisplayStyle.INLINE_BLOCK;
602     }
603 
604     /**
605      * Returns the value of the {@code placeholder} attribute.
606      *
607      * @return the value of the {@code placeholder} attribute
608      */
609     public String getPlaceholder() {
610         return getAttribute("placeholder");
611     }
612 
613     /**
614      * Sets the {@code placeholder} attribute.
615      *
616      * @param placeholder the {@code placeholder} attribute
617      */
618     public void setPlaceholder(final String placeholder) {
619         setAttribute("placeholder", placeholder);
620     }
621 
622     /**
623      * {@inheritDoc}
624      */
625     @Override
626     protected boolean isRequiredSupported() {
627         return true;
628     }
629 }