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