Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / 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 "core/html/HTMLTextAreaElement.h"
28
29 #include "bindings/core/v8/ExceptionState.h"
30 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
31 #include "core/CSSValueKeywords.h"
32 #include "core/HTMLNames.h"
33 #include "core/dom/Document.h"
34 #include "core/dom/ExceptionCode.h"
35 #include "core/dom/Text.h"
36 #include "core/dom/shadow/ShadowRoot.h"
37 #include "core/editing/FrameSelection.h"
38 #include "core/editing/SpellChecker.h"
39 #include "core/editing/TextIterator.h"
40 #include "core/events/BeforeTextInsertedEvent.h"
41 #include "core/events/Event.h"
42 #include "core/frame/FrameHost.h"
43 #include "core/frame/LocalFrame.h"
44 #include "core/html/FormDataList.h"
45 #include "core/html/forms/FormController.h"
46 #include "core/html/shadow/ShadowElementNames.h"
47 #include "core/html/shadow/TextControlInnerElements.h"
48 #include "core/page/Chrome.h"
49 #include "core/page/ChromeClient.h"
50 #include "core/rendering/RenderTextControlMultiLine.h"
51 #include "platform/text/PlatformLocale.h"
52 #include "wtf/StdLibExtras.h"
53 #include "wtf/text/StringBuilder.h"
54
55 namespace blink {
56
57 using namespace HTMLNames;
58
59 static const int defaultRows = 2;
60 static const int defaultCols = 20;
61
62 // On submission, LF characters are converted into CRLF.
63 // This function returns number of characters considering this.
64 static unsigned numberOfLineBreaks(const String& text)
65 {
66     unsigned length = text.length();
67     unsigned count = 0;
68     for (unsigned i = 0; i < length; i++) {
69         if (text[i] == '\n')
70             count++;
71     }
72     return count;
73 }
74
75 static inline unsigned computeLengthForSubmission(const String& text)
76 {
77     return text.length() + numberOfLineBreaks(text);
78 }
79
80 HTMLTextAreaElement::HTMLTextAreaElement(Document& document, HTMLFormElement* form)
81     : HTMLTextFormControlElement(textareaTag, document, form)
82     , m_rows(defaultRows)
83     , m_cols(defaultCols)
84     , m_wrap(SoftWrap)
85     , m_isDirty(false)
86     , m_valueIsUpToDate(true)
87 {
88     ScriptWrappable::init(this);
89 }
90
91 PassRefPtrWillBeRawPtr<HTMLTextAreaElement> HTMLTextAreaElement::create(Document& document, HTMLFormElement* form)
92 {
93     RefPtrWillBeRawPtr<HTMLTextAreaElement> textArea = adoptRefWillBeNoop(new HTMLTextAreaElement(document, form));
94     textArea->ensureUserAgentShadowRoot();
95     return textArea.release();
96 }
97
98 void HTMLTextAreaElement::didAddUserAgentShadowRoot(ShadowRoot& root)
99 {
100     root.appendChild(TextControlInnerEditorElement::create(document()));
101 }
102
103 const AtomicString& HTMLTextAreaElement::formControlType() const
104 {
105     DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea", AtomicString::ConstructFromLiteral));
106     return textarea;
107 }
108
109 FormControlState HTMLTextAreaElement::saveFormControlState() const
110 {
111     return m_isDirty ? FormControlState(value()) : FormControlState();
112 }
113
114 void HTMLTextAreaElement::restoreFormControlState(const FormControlState& state)
115 {
116     setValue(state[0]);
117 }
118
119 void HTMLTextAreaElement::childrenChanged(const ChildrenChange& change)
120 {
121     HTMLElement::childrenChanged(change);
122     setLastChangeWasNotUserEdit();
123     if (m_isDirty)
124         setInnerEditorValue(value());
125     else
126         setNonDirtyValue(defaultValue());
127 }
128
129 bool HTMLTextAreaElement::isPresentationAttribute(const QualifiedName& name) const
130 {
131     if (name == alignAttr) {
132         // Don't map 'align' attribute.  This matches what Firefox, Opera and IE do.
133         // See http://bugs.webkit.org/show_bug.cgi?id=7075
134         return false;
135     }
136
137     if (name == wrapAttr)
138         return true;
139     return HTMLTextFormControlElement::isPresentationAttribute(name);
140 }
141
142 void HTMLTextAreaElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
143 {
144     if (name == wrapAttr) {
145         if (shouldWrapText()) {
146             addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePreWrap);
147             addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord);
148         } else {
149             addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePre);
150             addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueNormal);
151         }
152     } else
153         HTMLTextFormControlElement::collectStyleForPresentationAttribute(name, value, style);
154 }
155
156 void HTMLTextAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
157 {
158     if (name == rowsAttr) {
159         int rows = value.toInt();
160         if (rows <= 0)
161             rows = defaultRows;
162         if (m_rows != rows) {
163             m_rows = rows;
164             if (renderer())
165                 renderer()->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation();
166         }
167     } else if (name == colsAttr) {
168         int cols = value.toInt();
169         if (cols <= 0)
170             cols = defaultCols;
171         if (m_cols != cols) {
172             m_cols = cols;
173             if (renderer())
174                 renderer()->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation();
175         }
176     } else if (name == wrapAttr) {
177         // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated.
178         // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4.
179         WrapMethod wrap;
180         if (equalIgnoringCase(value, "physical") || equalIgnoringCase(value, "hard") || equalIgnoringCase(value, "on"))
181             wrap = HardWrap;
182         else if (equalIgnoringCase(value, "off"))
183             wrap = NoWrap;
184         else
185             wrap = SoftWrap;
186         if (wrap != m_wrap) {
187             m_wrap = wrap;
188             if (renderer())
189                 renderer()->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation();
190         }
191     } else if (name == accesskeyAttr) {
192         // ignore for the moment
193     } else if (name == maxlengthAttr)
194         setNeedsValidityCheck();
195     else
196         HTMLTextFormControlElement::parseAttribute(name, value);
197 }
198
199 RenderObject* HTMLTextAreaElement::createRenderer(RenderStyle*)
200 {
201     return new RenderTextControlMultiLine(this);
202 }
203
204 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool)
205 {
206     if (name().isEmpty())
207         return false;
208
209     document().updateLayout();
210
211     const String& text = (m_wrap == HardWrap) ? valueWithHardLineBreaks() : value();
212     encoding.appendData(name(), text);
213
214     const AtomicString& dirnameAttrValue = fastGetAttribute(dirnameAttr);
215     if (!dirnameAttrValue.isNull())
216         encoding.appendData(dirnameAttrValue, directionForFormData());
217     return true;
218 }
219
220 void HTMLTextAreaElement::resetImpl()
221 {
222     setNonDirtyValue(defaultValue());
223 }
224
225 bool HTMLTextAreaElement::hasCustomFocusLogic() const
226 {
227     return true;
228 }
229
230 bool HTMLTextAreaElement::isKeyboardFocusable() const
231 {
232     // If a given text area can be focused at all, then it will always be keyboard focusable.
233     return isFocusable();
234 }
235
236 bool HTMLTextAreaElement::shouldShowFocusRingOnMouseFocus() const
237 {
238     return true;
239 }
240
241 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection)
242 {
243     if (!restorePreviousSelection)
244         setSelectionRange(0, 0);
245     else
246         restoreCachedSelection();
247
248     if (document().frame())
249         document().frame()->selection().revealSelection();
250 }
251
252 void HTMLTextAreaElement::defaultEventHandler(Event* event)
253 {
254     if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(EventNames::WheelEvent) || event->type() == EventTypeNames::blur))
255         forwardEvent(event);
256     else if (renderer() && event->isBeforeTextInsertedEvent())
257         handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event));
258
259     HTMLTextFormControlElement::defaultEventHandler(event);
260 }
261
262 void HTMLTextAreaElement::handleFocusEvent(Element*, FocusType)
263 {
264     if (LocalFrame* frame = document().frame())
265         frame->spellChecker().didBeginEditing(this);
266 }
267
268 void HTMLTextAreaElement::subtreeHasChanged()
269 {
270     setChangedSinceLastFormControlChangeEvent(true);
271     m_valueIsUpToDate = false;
272     setNeedsValidityCheck();
273     setAutofilled(false);
274
275     if (!focused())
276         return;
277
278     // When typing in a textarea, childrenChanged is not called, so we need to force the directionality check.
279     calculateAndAdjustDirectionality();
280
281     ASSERT(document().isActive());
282     document().frameHost()->chrome().client().didChangeValueInTextField(*this);
283 }
284
285 void HTMLTextAreaElement::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event) const
286 {
287     ASSERT(event);
288     ASSERT(renderer());
289     int signedMaxLength = maxLength();
290     if (signedMaxLength < 0)
291         return;
292     unsigned unsignedMaxLength = static_cast<unsigned>(signedMaxLength);
293
294     const String& currentValue = innerEditorValue();
295     unsigned currentLength = computeLengthForSubmission(currentValue);
296     if (currentLength + computeLengthForSubmission(event->text()) < unsignedMaxLength)
297         return;
298
299     // selectionLength represents the selection length of this text field to be
300     // removed by this insertion.
301     // If the text field has no focus, we don't need to take account of the
302     // selection length. The selection is the source of text drag-and-drop in
303     // that case, and nothing in the text field will be removed.
304     unsigned selectionLength = focused() ? computeLengthForSubmission(plainText(document().frame()->selection().selection().toNormalizedRange().get())) : 0;
305     ASSERT(currentLength >= selectionLength);
306     unsigned baseLength = currentLength - selectionLength;
307     unsigned appendableLength = unsignedMaxLength > baseLength ? unsignedMaxLength - baseLength : 0;
308     event->setText(sanitizeUserInputValue(event->text(), appendableLength));
309 }
310
311 String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength)
312 {
313     if (maxLength > 0 && U16_IS_LEAD(proposedValue[maxLength - 1]))
314         --maxLength;
315     return proposedValue.left(maxLength);
316 }
317
318 void HTMLTextAreaElement::updateValue() const
319 {
320     if (m_valueIsUpToDate)
321         return;
322
323     ASSERT(renderer());
324     m_value = innerEditorValue();
325     const_cast<HTMLTextAreaElement*>(this)->m_valueIsUpToDate = true;
326     const_cast<HTMLTextAreaElement*>(this)->notifyFormStateChanged();
327     m_isDirty = true;
328     const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility(false);
329 }
330
331 String HTMLTextAreaElement::value() const
332 {
333     updateValue();
334     return m_value;
335 }
336
337 void HTMLTextAreaElement::setValue(const String& value, TextFieldEventBehavior eventBehavior)
338 {
339     RefPtrWillBeRawPtr<HTMLTextAreaElement> protector(this);
340     setValueCommon(value, eventBehavior);
341     m_isDirty = true;
342     if (document().focusedElement() == this)
343         document().frameHost()->chrome().client().didUpdateTextOfFocusedElementByNonUserInput();
344 }
345
346 void HTMLTextAreaElement::setNonDirtyValue(const String& value)
347 {
348     setValueCommon(value, DispatchNoEvent, ChangeSelection);
349     m_isDirty = false;
350 }
351
352 void HTMLTextAreaElement::setValueCommon(const String& newValue, TextFieldEventBehavior eventBehavior, SelectionOption selectionOption)
353 {
354     // Code elsewhere normalizes line endings added by the user via the keyboard or pasting.
355     // We normalize line endings coming from JavaScript here.
356     String normalizedValue = newValue.isNull() ? "" : newValue;
357     normalizedValue.replace("\r\n", "\n");
358     normalizedValue.replace('\r', '\n');
359
360     // Return early because we don't want to trigger other side effects
361     // when the value isn't changing.
362     // FIXME: Simple early return doesn't match the Firefox ever.
363     // Remove these lines.
364     if (normalizedValue == value()) {
365         if (selectionOption == ChangeSelection) {
366             setNeedsValidityCheck();
367             if (isFinishedParsingChildren()) {
368                 // Set the caret to the end of the text value except for initialize.
369                 unsigned endOfString = m_value.length();
370                 setSelectionRange(endOfString, endOfString, SelectionHasNoDirection, NotChangeSelection);
371             }
372         }
373         return;
374     }
375
376     m_value = normalizedValue;
377     setInnerEditorValue(m_value);
378     if (eventBehavior == DispatchNoEvent)
379         setLastChangeWasNotUserEdit();
380     updatePlaceholderVisibility(false);
381     setNeedsStyleRecalc(SubtreeStyleChange);
382     m_suggestedValue = String();
383     setNeedsValidityCheck();
384     if (isFinishedParsingChildren()) {
385         // Set the caret to the end of the text value except for initialize.
386         unsigned endOfString = m_value.length();
387         setSelectionRange(endOfString, endOfString, SelectionHasNoDirection, NotChangeSelection);
388     }
389
390     notifyFormStateChanged();
391     if (eventBehavior == DispatchNoEvent) {
392         setTextAsOfLastFormControlChangeEvent(normalizedValue);
393     } else {
394         if (eventBehavior == DispatchInputAndChangeEvent)
395             dispatchFormControlInputEvent();
396         dispatchFormControlChangeEvent();
397     }
398 }
399
400 void HTMLTextAreaElement::setInnerEditorValue(const String& value)
401 {
402     HTMLTextFormControlElement::setInnerEditorValue(value);
403     m_valueIsUpToDate = true;
404 }
405
406 String HTMLTextAreaElement::defaultValue() const
407 {
408     StringBuilder value;
409
410     // Since there may be comments, ignore nodes other than text nodes.
411     for (Node* n = firstChild(); n; n = n->nextSibling()) {
412         if (n->isTextNode())
413             value.append(toText(n)->data());
414     }
415
416     return value.toString();
417 }
418
419 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
420 {
421     RefPtrWillBeRawPtr<Node> protectFromMutationEvents(this);
422
423     // To preserve comments, remove only the text nodes, then add a single text node.
424     WillBeHeapVector<RefPtrWillBeMember<Node> > textNodes;
425     for (Node* n = firstChild(); n; n = n->nextSibling()) {
426         if (n->isTextNode())
427             textNodes.append(n);
428     }
429     size_t size = textNodes.size();
430     for (size_t i = 0; i < size; ++i)
431         removeChild(textNodes[i].get(), IGNORE_EXCEPTION);
432
433     // Normalize line endings.
434     String value = defaultValue;
435     value.replace("\r\n", "\n");
436     value.replace('\r', '\n');
437
438     insertBefore(document().createTextNode(value), firstChild(), IGNORE_EXCEPTION);
439
440     if (!m_isDirty)
441         setNonDirtyValue(value);
442 }
443
444 int HTMLTextAreaElement::maxLength() const
445 {
446     bool ok;
447     int value = getAttribute(maxlengthAttr).string().toInt(&ok);
448     return ok && value >= 0 ? value : -1;
449 }
450
451 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionState& exceptionState)
452 {
453     if (newValue < 0)
454         exceptionState.throwDOMException(IndexSizeError, "The value provided (" + String::number(newValue) + ") is not positive or 0.");
455     else
456         setIntegralAttribute(maxlengthAttr, newValue);
457 }
458
459 String HTMLTextAreaElement::suggestedValue() const
460 {
461     return m_suggestedValue;
462 }
463
464 void HTMLTextAreaElement::setSuggestedValue(const String& value)
465 {
466     m_suggestedValue = value;
467
468     if (!value.isNull())
469         setInnerEditorValue(m_suggestedValue);
470     else
471         setInnerEditorValue(m_value);
472     updatePlaceholderVisibility(false);
473     setNeedsStyleRecalc(SubtreeStyleChange);
474 }
475
476 String HTMLTextAreaElement::validationMessage() const
477 {
478     if (!willValidate())
479         return String();
480
481     if (customError())
482         return customValidationMessage();
483
484     if (valueMissing())
485         return locale().queryString(blink::WebLocalizedString::ValidationValueMissing);
486
487     if (tooLong())
488         return locale().validationMessageTooLongText(computeLengthForSubmission(value()), maxLength());
489
490     return String();
491 }
492
493 bool HTMLTextAreaElement::valueMissing() const
494 {
495     return willValidate() && valueMissing(value());
496 }
497
498 bool HTMLTextAreaElement::tooLong() const
499 {
500     return willValidate() && tooLong(value(), CheckDirtyFlag);
501 }
502
503 bool HTMLTextAreaElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const
504 {
505     // Return false for the default value or value set by script even if it is
506     // longer than maxLength.
507     if (check == CheckDirtyFlag && !lastChangeWasUserEdit())
508         return false;
509
510     int max = maxLength();
511     if (max < 0)
512         return false;
513     return computeLengthForSubmission(value) > static_cast<unsigned>(max);
514 }
515
516 bool HTMLTextAreaElement::isValidValue(const String& candidate) const
517 {
518     return !valueMissing(candidate) && !tooLong(candidate, IgnoreDirtyFlag);
519 }
520
521 void HTMLTextAreaElement::accessKeyAction(bool)
522 {
523     focus();
524 }
525
526 void HTMLTextAreaElement::setCols(int cols)
527 {
528     setIntegralAttribute(colsAttr, cols);
529 }
530
531 void HTMLTextAreaElement::setRows(int rows)
532 {
533     setIntegralAttribute(rowsAttr, rows);
534 }
535
536 bool HTMLTextAreaElement::matchesReadOnlyPseudoClass() const
537 {
538     return isReadOnly();
539 }
540
541 bool HTMLTextAreaElement::matchesReadWritePseudoClass() const
542 {
543     return !isReadOnly();
544 }
545
546 void HTMLTextAreaElement::updatePlaceholderText()
547 {
548     HTMLElement* placeholder = placeholderElement();
549     const AtomicString& placeholderText = fastGetAttribute(placeholderAttr);
550     if (placeholderText.isEmpty()) {
551         if (placeholder)
552             userAgentShadowRoot()->removeChild(placeholder);
553         return;
554     }
555     if (!placeholder) {
556         RefPtrWillBeRawPtr<HTMLDivElement> newElement = HTMLDivElement::create(document());
557         placeholder = newElement.get();
558         placeholder->setShadowPseudoId(AtomicString("-webkit-input-placeholder", AtomicString::ConstructFromLiteral));
559         placeholder->setAttribute(idAttr, ShadowElementNames::placeholder());
560         userAgentShadowRoot()->insertBefore(placeholder, innerEditorElement()->nextSibling());
561     }
562     placeholder->setTextContent(placeholderText);
563 }
564
565 bool HTMLTextAreaElement::isInteractiveContent() const
566 {
567     return true;
568 }
569
570 bool HTMLTextAreaElement::supportsAutofocus() const
571 {
572     return true;
573 }
574
575 }