Unreviewed, rolling out r106909.
[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     ExceptionCode ec = 0;
88     ensureShadowRoot()->appendChild(TextControlInnerTextElement::create(document()), ec);
89 }
90
91 const AtomicString& HTMLTextAreaElement::formControlType() const
92 {
93     DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea"));
94     return textarea;
95 }
96
97 bool HTMLTextAreaElement::saveFormControlState(String& result) const
98 {
99     String currentValue = value();
100     if (currentValue == defaultValue())
101         return false;
102     result = currentValue;
103     return true;
104 }
105
106 void HTMLTextAreaElement::restoreFormControlState(const String& state)
107 {
108     setValue(state);
109 }
110
111 void HTMLTextAreaElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
112 {
113     setLastChangeWasNotUserEdit();
114     if (!m_isDirty)
115         setNonDirtyValue(defaultValue());
116     setInnerTextValue(value());
117     HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
118 }
119     
120 void HTMLTextAreaElement::parseAttribute(Attribute* attr)
121 {
122     if (attr->name() == rowsAttr) {
123         int rows = attr->value().toInt();
124         if (rows <= 0)
125             rows = defaultRows;
126         if (m_rows != rows) {
127             m_rows = rows;
128             if (renderer())
129                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
130         }
131     } else if (attr->name() == colsAttr) {
132         int cols = attr->value().toInt();
133         if (cols <= 0)
134             cols = defaultCols;
135         if (m_cols != cols) {
136             m_cols = cols;
137             if (renderer())
138                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
139         }
140     } else if (attr->name() == wrapAttr) {
141         // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated.
142         // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4.
143         WrapMethod wrap;
144         if (equalIgnoringCase(attr->value(), "physical") || equalIgnoringCase(attr->value(), "hard") || equalIgnoringCase(attr->value(), "on"))
145             wrap = HardWrap;
146         else if (equalIgnoringCase(attr->value(), "off"))
147             wrap = NoWrap;
148         else
149             wrap = SoftWrap;
150         if (wrap != m_wrap) {
151             m_wrap = wrap;
152
153             if (shouldWrapText()) {
154                 addCSSProperty(CSSPropertyWhiteSpace, CSSValuePreWrap);
155                 addCSSProperty(CSSPropertyWordWrap, CSSValueBreakWord);
156             } else {
157                 addCSSProperty(CSSPropertyWhiteSpace, CSSValuePre);
158                 addCSSProperty(CSSPropertyWordWrap, CSSValueNormal);
159             }
160
161             if (renderer())
162                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
163         }
164     } else if (attr->name() == accesskeyAttr) {
165         // ignore for the moment
166     } else if (attr->name() == alignAttr) {
167         // Don't map 'align' attribute.  This matches what Firefox, Opera and IE do.
168         // See http://bugs.webkit.org/show_bug.cgi?id=7075
169     } else if (attr->name() == maxlengthAttr)
170         setNeedsValidityCheck();
171     else
172         HTMLTextFormControlElement::parseAttribute(attr);
173 }
174
175 RenderObject* HTMLTextAreaElement::createRenderer(RenderArena* arena, RenderStyle*)
176 {
177     return new (arena) RenderTextControlMultiLine(this);
178 }
179
180 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool)
181 {
182     if (name().isEmpty())
183         return false;
184
185     document()->updateLayout();
186
187     const String& text = (m_wrap == HardWrap) ? valueWithHardLineBreaks() : value();
188     encoding.appendData(name(), text);
189
190     const AtomicString& dirnameAttrValue = fastGetAttribute(dirnameAttr);
191     if (!dirnameAttrValue.isNull())
192         encoding.appendData(dirnameAttrValue, directionForFormData());
193     return true;    
194 }
195
196 void HTMLTextAreaElement::reset()
197 {
198     setNonDirtyValue(defaultValue());
199 }
200
201 bool HTMLTextAreaElement::isKeyboardFocusable(KeyboardEvent*) const
202 {
203     // If a given text area can be focused at all, then it will always be keyboard focusable.
204     return isFocusable();
205 }
206
207 bool HTMLTextAreaElement::isMouseFocusable() const
208 {
209     return isFocusable();
210 }
211
212 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection)
213 {
214     if (!restorePreviousSelection || !hasCachedSelection()) {
215         // If this is the first focus, set a caret at the beginning of the text.  
216         // This matches some browsers' behavior; see bug 11746 Comment #15.
217         // http://bugs.webkit.org/show_bug.cgi?id=11746#c15
218         setSelectionRange(0, 0);
219     } else
220         restoreCachedSelection();
221
222     if (document()->frame())
223         document()->frame()->selection()->revealSelection();
224 }
225
226 void HTMLTextAreaElement::defaultEventHandler(Event* event)
227 {
228     if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(eventNames().interfaceForWheelEvent) || event->type() == eventNames().blurEvent))
229         forwardEvent(event);
230     else if (renderer() && event->isBeforeTextInsertedEvent())
231         handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event));
232
233     HTMLTextFormControlElement::defaultEventHandler(event);
234 }
235
236 void HTMLTextAreaElement::subtreeHasChanged()
237 {
238     setChangedSinceLastFormControlChangeEvent(true);
239     setFormControlValueMatchesRenderer(false);
240     setNeedsValidityCheck();
241
242     if (!focused())
243         return;
244
245     if (Frame* frame = document()->frame())
246         frame->editor()->textDidChangeInTextArea(this);
247     // When typing in a textarea, childrenChanged is not called, so we need to force the directionality check.
248     calculateAndAdjustDirectionality();
249 }
250
251 void HTMLTextAreaElement::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event) const
252 {
253     ASSERT(event);
254     ASSERT(renderer());
255     int signedMaxLength = maxLength();
256     if (signedMaxLength < 0)
257         return;
258     unsigned unsignedMaxLength = static_cast<unsigned>(signedMaxLength);
259
260     unsigned currentLength = computeLengthForSubmission(innerTextValue());
261     // selectionLength represents the selection length of this text field to be
262     // removed by this insertion.
263     // If the text field has no focus, we don't need to take account of the
264     // selection length. The selection is the source of text drag-and-drop in
265     // that case, and nothing in the text field will be removed.
266     unsigned selectionLength = focused() ? computeLengthForSubmission(plainText(document()->frame()->selection()->selection().toNormalizedRange().get())) : 0;
267     ASSERT(currentLength >= selectionLength);
268     unsigned baseLength = currentLength - selectionLength;
269     unsigned appendableLength = unsignedMaxLength > baseLength ? unsignedMaxLength - baseLength : 0;
270     event->setText(sanitizeUserInputValue(event->text(), appendableLength));
271 }
272
273 String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength)
274 {
275     return proposedValue.left(numCharactersInGraphemeClusters(proposedValue, maxLength));
276 }
277
278 HTMLElement* HTMLTextAreaElement::innerTextElement() const
279 {
280     Node* node = shadowRoot()->firstChild();
281     ASSERT(!node || node->hasTagName(divTag));
282     return toHTMLElement(node);
283 }
284
285 void HTMLTextAreaElement::rendererWillBeDestroyed()
286 {
287     updateValue();
288 }
289
290 void HTMLTextAreaElement::updateValue() const
291 {
292     if (formControlValueMatchesRenderer())
293         return;
294
295     ASSERT(renderer());
296     m_value = innerTextValue();
297     const_cast<HTMLTextAreaElement*>(this)->setFormControlValueMatchesRenderer(true);
298     const_cast<HTMLTextAreaElement*>(this)->notifyFormStateChanged();
299     m_isDirty = true;
300     m_wasModifiedByUser = true;
301     const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility(false);
302 }
303
304 String HTMLTextAreaElement::value() const
305 {
306     updateValue();
307     return m_value;
308 }
309
310 void HTMLTextAreaElement::setValue(const String& value)
311 {
312     setValueCommon(value);
313     m_isDirty = true;
314     setNeedsValidityCheck();
315 }
316
317 void HTMLTextAreaElement::setNonDirtyValue(const String& value)
318 {
319     setValueCommon(value);
320     m_isDirty = false;
321     setNeedsValidityCheck();
322 }
323
324 void HTMLTextAreaElement::setValueCommon(const String& newValue)
325 {
326     m_wasModifiedByUser = false;
327     // Code elsewhere normalizes line endings added by the user via the keyboard or pasting.
328     // We normalize line endings coming from JavaScript here.
329     String normalizedValue = newValue.isNull() ? "" : newValue;
330     normalizedValue.replace("\r\n", "\n");
331     normalizedValue.replace('\r', '\n');
332
333     // Return early because we don't want to move the caret or trigger other side effects
334     // when the value isn't changing. This matches Firefox behavior, at least.
335     if (normalizedValue == value())
336         return;
337
338     m_value = normalizedValue;
339     setInnerTextValue(m_value);
340     setLastChangeWasNotUserEdit();
341     updatePlaceholderVisibility(false);
342     setNeedsStyleRecalc();
343     setFormControlValueMatchesRenderer(true);
344
345     // Set the caret to the end of the text value.
346     if (document()->focusedNode() == this) {
347         unsigned endOfString = m_value.length();
348         setSelectionRange(endOfString, endOfString);
349     }
350
351     notifyFormStateChanged();
352     setTextAsOfLastFormControlChangeEvent(normalizedValue);
353 }
354
355 String HTMLTextAreaElement::defaultValue() const
356 {
357     String value = "";
358
359     // Since there may be comments, ignore nodes other than text nodes.
360     for (Node* n = firstChild(); n; n = n->nextSibling()) {
361         if (n->isTextNode())
362             value += static_cast<Text*>(n)->data();
363     }
364
365     return value;
366 }
367
368 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
369 {
370     // To preserve comments, remove only the text nodes, then add a single text node.
371
372     Vector<RefPtr<Node> > textNodes;
373     for (Node* n = firstChild(); n; n = n->nextSibling()) {
374         if (n->isTextNode())
375             textNodes.append(n);
376     }
377     ExceptionCode ec;
378     size_t size = textNodes.size();
379     for (size_t i = 0; i < size; ++i)
380         removeChild(textNodes[i].get(), ec);
381
382     // Normalize line endings.
383     String value = defaultValue;
384     value.replace("\r\n", "\n");
385     value.replace('\r', '\n');
386
387     insertBefore(document()->createTextNode(value), firstChild(), ec);
388
389     if (!m_isDirty)
390         setNonDirtyValue(value);
391 }
392
393 int HTMLTextAreaElement::maxLength() const
394 {
395     bool ok;
396     int value = getAttribute(maxlengthAttr).string().toInt(&ok);
397     return ok && value >= 0 ? value : -1;
398 }
399
400 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionCode& ec)
401 {
402     if (newValue < 0)
403         ec = INDEX_SIZE_ERR;
404     else
405         setAttribute(maxlengthAttr, String::number(newValue));
406 }
407
408 bool HTMLTextAreaElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const
409 {
410     // Return false for the default value or value set by script even if it is
411     // longer than maxLength.
412     if (check == CheckDirtyFlag && !m_wasModifiedByUser)
413         return false;
414
415     int max = maxLength();
416     if (max < 0)
417         return false;
418     return computeLengthForSubmission(value) > static_cast<unsigned>(max);
419 }
420
421 bool HTMLTextAreaElement::isValidValue(const String& candidate) const
422 {
423     return !valueMissing(candidate) && !tooLong(candidate, IgnoreDirtyFlag);
424 }
425
426 void HTMLTextAreaElement::accessKeyAction(bool)
427 {
428     focus();
429 }
430
431 void HTMLTextAreaElement::setCols(int cols)
432 {
433     setAttribute(colsAttr, String::number(cols));
434 }
435
436 void HTMLTextAreaElement::setRows(int rows)
437 {
438     setAttribute(rowsAttr, String::number(rows));
439 }
440
441 bool HTMLTextAreaElement::shouldUseInputMethod()
442 {
443     return true;
444 }
445
446 HTMLElement* HTMLTextAreaElement::placeholderElement() const
447 {
448     return m_placeholder.get();
449 }
450
451 void HTMLTextAreaElement::updatePlaceholderText()
452 {
453     ExceptionCode ec = 0;
454     String placeholderText = strippedPlaceholder();
455     if (placeholderText.isEmpty()) {
456         if (m_placeholder) {
457             shadowRoot()->removeChild(m_placeholder.get(), ec);
458             ASSERT(!ec);
459             m_placeholder.clear();
460         }
461         return;
462     }
463     if (!m_placeholder) {
464         m_placeholder = HTMLDivElement::create(document());
465         m_placeholder->setShadowPseudoId("-webkit-input-placeholder");
466         shadowRoot()->insertBefore(m_placeholder, shadowRoot()->firstChild()->nextSibling(), ec);
467         ASSERT(!ec);
468     }
469     m_placeholder->setInnerText(placeholderText, ec);
470     ASSERT(!ec);
471 }
472
473 }