619667022de1e0255f6b4e06e9d045cd0b762749
[WebKit-https.git] / Source / WebCore / html / HTMLTextAreaElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
5  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
6  *           (C) 2006 Alexey Proskuryakov (ap@nypop.com)
7  * Copyright (C) 2007 Samuel Weinig (sam@webkit.org)
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public License
20  * along with this library; see the file COPYING.LIB.  If not, write to
21  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  *
24  */
25
26 #include "config.h"
27 #include "HTMLTextAreaElement.h"
28
29 #include "Attribute.h"
30 #include "BeforeTextInsertedEvent.h"
31 #include "CSSValueKeywords.h"
32 #include "Document.h"
33 #include "Event.h"
34 #include "EventNames.h"
35 #include "ExceptionCode.h"
36 #include "FormDataList.h"
37 #include "Frame.h"
38 #include "HTMLNames.h"
39 #include "RenderTextControlMultiLine.h"
40 #include "ShadowRoot.h"
41 #include "Text.h"
42 #include "TextControlInnerElements.h"
43 #include "TextIterator.h"
44 #include <wtf/StdLibExtras.h>
45
46 namespace WebCore {
47
48 using namespace HTMLNames;
49
50 static const int defaultRows = 2;
51 static const int defaultCols = 20;
52
53 // On submission, LF characters are converted into CRLF.
54 // This function returns number of characters considering this.
55 static unsigned computeLengthForSubmission(const String& text)
56 {
57     unsigned count =  numGraphemeClusters(text);
58     unsigned length = text.length();
59     for (unsigned i = 0; i < length; i++) {
60         if (text[i] == '\n')
61             count++;
62     }
63     return count;
64 }
65
66 HTMLTextAreaElement::HTMLTextAreaElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
67     : HTMLTextFormControlElement(tagName, document, form)
68     , m_rows(defaultRows)
69     , m_cols(defaultCols)
70     , m_wrap(SoftWrap)
71     , m_isDirty(false)
72     , m_wasModifiedByUser(false)
73 {
74     ASSERT(hasTagName(textareaTag));
75     setFormControlValueMatchesRenderer(true);
76 }
77
78 PassRefPtr<HTMLTextAreaElement> HTMLTextAreaElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
79 {
80     RefPtr<HTMLTextAreaElement> textArea = adoptRef(new HTMLTextAreaElement(tagName, document, form));
81     textArea->createShadowSubtree();
82     return textArea.release();
83 }
84
85 void HTMLTextAreaElement::createShadowSubtree()
86 {
87     ASSERT(!shadowRoot());
88     RefPtr<ShadowRoot> root = ShadowRoot::create(this, ASSERT_NO_EXCEPTION);
89     root->appendChild(TextControlInnerTextElement::create(document()), ASSERT_NO_EXCEPTION);
90 }
91
92 const AtomicString& HTMLTextAreaElement::formControlType() const
93 {
94     DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea"));
95     return textarea;
96 }
97
98 bool HTMLTextAreaElement::saveFormControlState(String& result) const
99 {
100     String currentValue = value();
101     if (currentValue == defaultValue())
102         return false;
103     result = currentValue;
104     return true;
105 }
106
107 void HTMLTextAreaElement::restoreFormControlState(const String& state)
108 {
109     setValue(state);
110 }
111
112 void HTMLTextAreaElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
113 {
114     setLastChangeWasNotUserEdit();
115     if (!m_isDirty)
116         setNonDirtyValue(defaultValue());
117     setInnerTextValue(value());
118     HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
119 }
120     
121 void HTMLTextAreaElement::parseAttribute(Attribute* attr)
122 {
123     if (attr->name() == rowsAttr) {
124         int rows = attr->value().toInt();
125         if (rows <= 0)
126             rows = defaultRows;
127         if (m_rows != rows) {
128             m_rows = rows;
129             if (renderer())
130                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
131         }
132     } else if (attr->name() == colsAttr) {
133         int cols = attr->value().toInt();
134         if (cols <= 0)
135             cols = defaultCols;
136         if (m_cols != cols) {
137             m_cols = cols;
138             if (renderer())
139                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
140         }
141     } else if (attr->name() == wrapAttr) {
142         // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated.
143         // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4.
144         WrapMethod wrap;
145         if (equalIgnoringCase(attr->value(), "physical") || equalIgnoringCase(attr->value(), "hard") || equalIgnoringCase(attr->value(), "on"))
146             wrap = HardWrap;
147         else if (equalIgnoringCase(attr->value(), "off"))
148             wrap = NoWrap;
149         else
150             wrap = SoftWrap;
151         if (wrap != m_wrap) {
152             m_wrap = wrap;
153
154             if (shouldWrapText()) {
155                 addCSSProperty(CSSPropertyWhiteSpace, CSSValuePreWrap);
156                 addCSSProperty(CSSPropertyWordWrap, CSSValueBreakWord);
157             } else {
158                 addCSSProperty(CSSPropertyWhiteSpace, CSSValuePre);
159                 addCSSProperty(CSSPropertyWordWrap, CSSValueNormal);
160             }
161
162             if (renderer())
163                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
164         }
165     } else if (attr->name() == accesskeyAttr) {
166         // ignore for the moment
167     } else if (attr->name() == alignAttr) {
168         // Don't map 'align' attribute.  This matches what Firefox, Opera and IE do.
169         // See http://bugs.webkit.org/show_bug.cgi?id=7075
170     } else if (attr->name() == maxlengthAttr)
171         setNeedsValidityCheck();
172     else
173         HTMLTextFormControlElement::parseAttribute(attr);
174 }
175
176 RenderObject* HTMLTextAreaElement::createRenderer(RenderArena* arena, RenderStyle*)
177 {
178     return new (arena) RenderTextControlMultiLine(this);
179 }
180
181 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool)
182 {
183     if (name().isEmpty())
184         return false;
185
186     document()->updateLayout();
187
188     const String& text = (m_wrap == HardWrap) ? valueWithHardLineBreaks() : value();
189     encoding.appendData(name(), text);
190
191     const AtomicString& dirnameAttrValue = fastGetAttribute(dirnameAttr);
192     if (!dirnameAttrValue.isNull())
193         encoding.appendData(dirnameAttrValue, directionForFormData());
194     return true;    
195 }
196
197 void HTMLTextAreaElement::reset()
198 {
199     setNonDirtyValue(defaultValue());
200 }
201
202 bool HTMLTextAreaElement::isKeyboardFocusable(KeyboardEvent*) const
203 {
204     // If a given text area can be focused at all, then it will always be keyboard focusable.
205     return isFocusable();
206 }
207
208 bool HTMLTextAreaElement::isMouseFocusable() const
209 {
210     return isFocusable();
211 }
212
213 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection)
214 {
215     if (!restorePreviousSelection || !hasCachedSelection()) {
216         // If this is the first focus, set a caret at the beginning of the text.  
217         // This matches some browsers' behavior; see bug 11746 Comment #15.
218         // http://bugs.webkit.org/show_bug.cgi?id=11746#c15
219         setSelectionRange(0, 0);
220     } else
221         restoreCachedSelection();
222
223     if (document()->frame())
224         document()->frame()->selection()->revealSelection();
225 }
226
227 void HTMLTextAreaElement::defaultEventHandler(Event* event)
228 {
229     if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(eventNames().interfaceForWheelEvent) || event->type() == eventNames().blurEvent))
230         forwardEvent(event);
231     else if (renderer() && event->isBeforeTextInsertedEvent())
232         handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event));
233
234     HTMLTextFormControlElement::defaultEventHandler(event);
235 }
236
237 void HTMLTextAreaElement::subtreeHasChanged()
238 {
239     setChangedSinceLastFormControlChangeEvent(true);
240     setFormControlValueMatchesRenderer(false);
241     setNeedsValidityCheck();
242
243     if (!focused())
244         return;
245
246     if (Frame* frame = document()->frame())
247         frame->editor()->textDidChangeInTextArea(this);
248     // When typing in a textarea, childrenChanged is not called, so we need to force the directionality check.
249     calculateAndAdjustDirectionality();
250 }
251
252 void HTMLTextAreaElement::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event) const
253 {
254     ASSERT(event);
255     ASSERT(renderer());
256     int signedMaxLength = maxLength();
257     if (signedMaxLength < 0)
258         return;
259     unsigned unsignedMaxLength = static_cast<unsigned>(signedMaxLength);
260
261     unsigned currentLength = computeLengthForSubmission(innerTextValue());
262     // selectionLength represents the selection length of this text field to be
263     // removed by this insertion.
264     // If the text field has no focus, we don't need to take account of the
265     // selection length. The selection is the source of text drag-and-drop in
266     // that case, and nothing in the text field will be removed.
267     unsigned selectionLength = focused() ? computeLengthForSubmission(plainText(document()->frame()->selection()->selection().toNormalizedRange().get())) : 0;
268     ASSERT(currentLength >= selectionLength);
269     unsigned baseLength = currentLength - selectionLength;
270     unsigned appendableLength = unsignedMaxLength > baseLength ? unsignedMaxLength - baseLength : 0;
271     event->setText(sanitizeUserInputValue(event->text(), appendableLength));
272 }
273
274 String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength)
275 {
276     return proposedValue.left(numCharactersInGraphemeClusters(proposedValue, maxLength));
277 }
278
279 HTMLElement* HTMLTextAreaElement::innerTextElement() const
280 {
281     Node* node = shadowRoot()->firstChild();
282     ASSERT(!node || node->hasTagName(divTag));
283     return toHTMLElement(node);
284 }
285
286 void HTMLTextAreaElement::rendererWillBeDestroyed()
287 {
288     updateValue();
289 }
290
291 void HTMLTextAreaElement::updateValue() const
292 {
293     if (formControlValueMatchesRenderer())
294         return;
295
296     ASSERT(renderer());
297     m_value = innerTextValue();
298     const_cast<HTMLTextAreaElement*>(this)->setFormControlValueMatchesRenderer(true);
299     const_cast<HTMLTextAreaElement*>(this)->notifyFormStateChanged();
300     m_isDirty = true;
301     m_wasModifiedByUser = true;
302     const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility(false);
303 }
304
305 String HTMLTextAreaElement::value() const
306 {
307     updateValue();
308     return m_value;
309 }
310
311 void HTMLTextAreaElement::setValue(const String& value)
312 {
313     setValueCommon(value);
314     m_isDirty = true;
315     setNeedsValidityCheck();
316 }
317
318 void HTMLTextAreaElement::setNonDirtyValue(const String& value)
319 {
320     setValueCommon(value);
321     m_isDirty = false;
322     setNeedsValidityCheck();
323 }
324
325 void HTMLTextAreaElement::setValueCommon(const String& newValue)
326 {
327     m_wasModifiedByUser = false;
328     // Code elsewhere normalizes line endings added by the user via the keyboard or pasting.
329     // We normalize line endings coming from JavaScript here.
330     String normalizedValue = newValue.isNull() ? "" : newValue;
331     normalizedValue.replace("\r\n", "\n");
332     normalizedValue.replace('\r', '\n');
333
334     // Return early because we don't want to move the caret or trigger other side effects
335     // when the value isn't changing. This matches Firefox behavior, at least.
336     if (normalizedValue == value())
337         return;
338
339     m_value = normalizedValue;
340     setInnerTextValue(m_value);
341     setLastChangeWasNotUserEdit();
342     updatePlaceholderVisibility(false);
343     setNeedsStyleRecalc();
344     setFormControlValueMatchesRenderer(true);
345
346     // Set the caret to the end of the text value.
347     if (document()->focusedNode() == this) {
348         unsigned endOfString = m_value.length();
349         setSelectionRange(endOfString, endOfString);
350     }
351
352     notifyFormStateChanged();
353     setTextAsOfLastFormControlChangeEvent(normalizedValue);
354 }
355
356 String HTMLTextAreaElement::defaultValue() const
357 {
358     String value = "";
359
360     // Since there may be comments, ignore nodes other than text nodes.
361     for (Node* n = firstChild(); n; n = n->nextSibling()) {
362         if (n->isTextNode())
363             value += static_cast<Text*>(n)->data();
364     }
365
366     return value;
367 }
368
369 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
370 {
371     // To preserve comments, remove only the text nodes, then add a single text node.
372
373     Vector<RefPtr<Node> > textNodes;
374     for (Node* n = firstChild(); n; n = n->nextSibling()) {
375         if (n->isTextNode())
376             textNodes.append(n);
377     }
378     ExceptionCode ec;
379     size_t size = textNodes.size();
380     for (size_t i = 0; i < size; ++i)
381         removeChild(textNodes[i].get(), ec);
382
383     // Normalize line endings.
384     String value = defaultValue;
385     value.replace("\r\n", "\n");
386     value.replace('\r', '\n');
387
388     insertBefore(document()->createTextNode(value), firstChild(), ec);
389
390     if (!m_isDirty)
391         setNonDirtyValue(value);
392 }
393
394 int HTMLTextAreaElement::maxLength() const
395 {
396     bool ok;
397     int value = getAttribute(maxlengthAttr).string().toInt(&ok);
398     return ok && value >= 0 ? value : -1;
399 }
400
401 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionCode& ec)
402 {
403     if (newValue < 0)
404         ec = INDEX_SIZE_ERR;
405     else
406         setAttribute(maxlengthAttr, String::number(newValue));
407 }
408
409 bool HTMLTextAreaElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const
410 {
411     // Return false for the default value or value set by script even if it is
412     // longer than maxLength.
413     if (check == CheckDirtyFlag && !m_wasModifiedByUser)
414         return false;
415
416     int max = maxLength();
417     if (max < 0)
418         return false;
419     return computeLengthForSubmission(value) > static_cast<unsigned>(max);
420 }
421
422 bool HTMLTextAreaElement::isValidValue(const String& candidate) const
423 {
424     return !valueMissing(candidate) && !tooLong(candidate, IgnoreDirtyFlag);
425 }
426
427 void HTMLTextAreaElement::accessKeyAction(bool)
428 {
429     focus();
430 }
431
432 void HTMLTextAreaElement::setCols(int cols)
433 {
434     setAttribute(colsAttr, String::number(cols));
435 }
436
437 void HTMLTextAreaElement::setRows(int rows)
438 {
439     setAttribute(rowsAttr, String::number(rows));
440 }
441
442 bool HTMLTextAreaElement::shouldUseInputMethod()
443 {
444     return true;
445 }
446
447 HTMLElement* HTMLTextAreaElement::placeholderElement() const
448 {
449     return m_placeholder.get();
450 }
451
452 void HTMLTextAreaElement::updatePlaceholderText()
453 {
454     ExceptionCode ec = 0;
455     String placeholderText = strippedPlaceholder();
456     if (placeholderText.isEmpty()) {
457         if (m_placeholder) {
458             shadowRoot()->removeChild(m_placeholder.get(), ec);
459             ASSERT(!ec);
460             m_placeholder.clear();
461         }
462         return;
463     }
464     if (!m_placeholder) {
465         m_placeholder = HTMLDivElement::create(document());
466         m_placeholder->setShadowPseudoId("-webkit-input-placeholder");
467         shadowRoot()->insertBefore(m_placeholder, shadowRoot()->firstChild()->nextSibling(), ec);
468         ASSERT(!ec);
469     }
470     m_placeholder->setInnerText(placeholderText, ec);
471     ASSERT(!ec);
472 }
473
474 }