Update To 11.40.268.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/parser/HTMLParserIdioms.h"
47 #include "core/html/shadow/ShadowElementNames.h"
48 #include "core/html/shadow/TextControlInnerElements.h"
49 #include "core/page/Chrome.h"
50 #include "core/page/ChromeClient.h"
51 #include "core/rendering/RenderTextControlMultiLine.h"
52 #include "platform/text/PlatformLocale.h"
53 #include "wtf/StdLibExtras.h"
54 #include "wtf/text/StringBuilder.h"
55
56 namespace blink {
57
58 using namespace HTMLNames;
59
60 static const int defaultRows = 2;
61 static const int defaultCols = 20;
62
63 // On submission, LF characters are converted into CRLF.
64 // This function returns number of characters considering this.
65 static unsigned numberOfLineBreaks(const String& text)
66 {
67     unsigned length = text.length();
68     unsigned count = 0;
69     for (unsigned i = 0; i < length; i++) {
70         if (text[i] == '\n')
71             count++;
72     }
73     return count;
74 }
75
76 static inline unsigned computeLengthForSubmission(const String& text)
77 {
78     return text.length() + numberOfLineBreaks(text);
79 }
80
81 HTMLTextAreaElement::HTMLTextAreaElement(Document& document, HTMLFormElement* form)
82     : HTMLTextFormControlElement(textareaTag, document, form)
83     , m_rows(defaultRows)
84     , m_cols(defaultCols)
85     , m_wrap(SoftWrap)
86     , m_isDirty(false)
87     , m_valueIsUpToDate(true)
88 {
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 if (name == minlengthAttr) {
196         setNeedsValidityCheck();
197     } else
198         HTMLTextFormControlElement::parseAttribute(name, value);
199 }
200
201 RenderObject* HTMLTextAreaElement::createRenderer(RenderStyle*)
202 {
203     return new RenderTextControlMultiLine(this);
204 }
205
206 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool)
207 {
208     if (name().isEmpty())
209         return false;
210
211     document().updateLayout();
212
213     const String& text = (m_wrap == HardWrap) ? valueWithHardLineBreaks() : value();
214     encoding.appendData(name(), text);
215
216     const AtomicString& dirnameAttrValue = fastGetAttribute(dirnameAttr);
217     if (!dirnameAttrValue.isNull())
218         encoding.appendData(dirnameAttrValue, directionForFormData());
219     return true;
220 }
221
222 void HTMLTextAreaElement::resetImpl()
223 {
224     setNonDirtyValue(defaultValue());
225 }
226
227 bool HTMLTextAreaElement::hasCustomFocusLogic() const
228 {
229     return true;
230 }
231
232 bool HTMLTextAreaElement::isKeyboardFocusable() const
233 {
234     // If a given text area can be focused at all, then it will always be keyboard focusable.
235     return isFocusable();
236 }
237
238 bool HTMLTextAreaElement::shouldShowFocusRingOnMouseFocus() const
239 {
240     return true;
241 }
242
243 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection)
244 {
245     if (!restorePreviousSelection)
246         setSelectionRange(0, 0);
247     else
248         restoreCachedSelection();
249
250     if (document().frame())
251         document().frame()->selection().revealSelection();
252 }
253
254 void HTMLTextAreaElement::defaultEventHandler(Event* event)
255 {
256     if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(EventNames::WheelEvent) || event->type() == EventTypeNames::blur))
257         forwardEvent(event);
258     else if (renderer() && event->isBeforeTextInsertedEvent())
259         handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event));
260
261     HTMLTextFormControlElement::defaultEventHandler(event);
262 }
263
264 void HTMLTextAreaElement::handleFocusEvent(Element*, FocusType)
265 {
266     if (LocalFrame* frame = document().frame())
267         frame->spellChecker().didBeginEditing(this);
268 }
269
270 void HTMLTextAreaElement::subtreeHasChanged()
271 {
272     setChangedSinceLastFormControlChangeEvent(true);
273     m_valueIsUpToDate = false;
274     setNeedsValidityCheck();
275     setAutofilled(false);
276
277     if (!focused())
278         return;
279
280     // When typing in a textarea, childrenChanged is not called, so we need to force the directionality check.
281     calculateAndAdjustDirectionality();
282
283     ASSERT(document().isActive());
284     document().frameHost()->chrome().client().didChangeValueInTextField(*this);
285 }
286
287 void HTMLTextAreaElement::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event) const
288 {
289     ASSERT(event);
290     ASSERT(renderer());
291     int signedMaxLength = maxLength();
292     if (signedMaxLength < 0)
293         return;
294     unsigned unsignedMaxLength = static_cast<unsigned>(signedMaxLength);
295
296     const String& currentValue = innerEditorValue();
297     unsigned currentLength = computeLengthForSubmission(currentValue);
298     if (currentLength + computeLengthForSubmission(event->text()) < unsignedMaxLength)
299         return;
300
301     // selectionLength represents the selection length of this text field to be
302     // removed by this insertion.
303     // If the text field has no focus, we don't need to take account of the
304     // selection length. The selection is the source of text drag-and-drop in
305     // that case, and nothing in the text field will be removed.
306     unsigned selectionLength = 0;
307     if (focused()) {
308         Position start, end;
309         document().frame()->selection().selection().toNormalizedPositions(start, end);
310         selectionLength = computeLengthForSubmission(plainText(start, end));
311     }
312     ASSERT(currentLength >= selectionLength);
313     unsigned baseLength = currentLength - selectionLength;
314     unsigned appendableLength = unsignedMaxLength > baseLength ? unsignedMaxLength - baseLength : 0;
315     event->setText(sanitizeUserInputValue(event->text(), appendableLength));
316 }
317
318 String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength)
319 {
320     if (maxLength > 0 && U16_IS_LEAD(proposedValue[maxLength - 1]))
321         --maxLength;
322     return proposedValue.left(maxLength);
323 }
324
325 void HTMLTextAreaElement::updateValue() const
326 {
327     if (m_valueIsUpToDate)
328         return;
329
330     m_value = innerEditorValue();
331     const_cast<HTMLTextAreaElement*>(this)->m_valueIsUpToDate = true;
332     const_cast<HTMLTextAreaElement*>(this)->notifyFormStateChanged();
333     m_isDirty = true;
334     const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility(false);
335 }
336
337 String HTMLTextAreaElement::value() const
338 {
339     updateValue();
340     return m_value;
341 }
342
343 void HTMLTextAreaElement::setValue(const String& value, TextFieldEventBehavior eventBehavior)
344 {
345     RefPtrWillBeRawPtr<HTMLTextAreaElement> protector(this);
346     setValueCommon(value, eventBehavior);
347     m_isDirty = true;
348     if (document().focusedElement() == this)
349         document().frameHost()->chrome().client().didUpdateTextOfFocusedElementByNonUserInput();
350 }
351
352 void HTMLTextAreaElement::setNonDirtyValue(const String& value)
353 {
354     setValueCommon(value, DispatchNoEvent, SetSeletion);
355     m_isDirty = false;
356 }
357
358 void HTMLTextAreaElement::setValueCommon(const String& newValue, TextFieldEventBehavior eventBehavior, SetValueCommonOption setValueOption)
359 {
360     // Code elsewhere normalizes line endings added by the user via the keyboard or pasting.
361     // We normalize line endings coming from JavaScript here.
362     String normalizedValue = newValue.isNull() ? "" : newValue;
363     normalizedValue.replace("\r\n", "\n");
364     normalizedValue.replace('\r', '\n');
365
366     // Return early because we don't want to trigger other side effects
367     // when the value isn't changing.
368     // FIXME: Simple early return doesn't match the Firefox ever.
369     // Remove these lines.
370     if (normalizedValue == value()) {
371         if (setValueOption == SetSeletion) {
372             setNeedsValidityCheck();
373             if (isFinishedParsingChildren()) {
374                 // Set the caret to the end of the text value except for initialize.
375                 unsigned endOfString = m_value.length();
376                 setSelectionRange(endOfString, endOfString, SelectionHasNoDirection, ChangeSelectionIfFocused);
377             }
378         }
379         return;
380     }
381
382     m_value = normalizedValue;
383     setInnerEditorValue(m_value);
384     if (eventBehavior == DispatchNoEvent)
385         setLastChangeWasNotUserEdit();
386     updatePlaceholderVisibility(false);
387     setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::ControlValue));
388     m_suggestedValue = String();
389     setNeedsValidityCheck();
390     if (isFinishedParsingChildren()) {
391         // Set the caret to the end of the text value except for initialize.
392         unsigned endOfString = m_value.length();
393         setSelectionRange(endOfString, endOfString, SelectionHasNoDirection, ChangeSelectionIfFocused);
394     }
395
396     notifyFormStateChanged();
397     if (eventBehavior == DispatchNoEvent) {
398         setTextAsOfLastFormControlChangeEvent(normalizedValue);
399     } else {
400         if (eventBehavior == DispatchInputAndChangeEvent)
401             dispatchFormControlInputEvent();
402         dispatchFormControlChangeEvent();
403     }
404 }
405
406 void HTMLTextAreaElement::setInnerEditorValue(const String& value)
407 {
408     HTMLTextFormControlElement::setInnerEditorValue(value);
409     m_valueIsUpToDate = true;
410 }
411
412 String HTMLTextAreaElement::defaultValue() const
413 {
414     StringBuilder value;
415
416     // Since there may be comments, ignore nodes other than text nodes.
417     for (Node* n = firstChild(); n; n = n->nextSibling()) {
418         if (n->isTextNode())
419             value.append(toText(n)->data());
420     }
421
422     return value.toString();
423 }
424
425 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
426 {
427     RefPtrWillBeRawPtr<Node> protectFromMutationEvents(this);
428
429     // To preserve comments, remove only the text nodes, then add a single text node.
430     WillBeHeapVector<RefPtrWillBeMember<Node>> textNodes;
431     for (Node* n = firstChild(); n; n = n->nextSibling()) {
432         if (n->isTextNode())
433             textNodes.append(n);
434     }
435     size_t size = textNodes.size();
436     for (size_t i = 0; i < size; ++i)
437         removeChild(textNodes[i].get(), IGNORE_EXCEPTION);
438
439     // Normalize line endings.
440     String value = defaultValue;
441     value.replace("\r\n", "\n");
442     value.replace('\r', '\n');
443
444     insertBefore(document().createTextNode(value), firstChild(), IGNORE_EXCEPTION);
445
446     if (!m_isDirty)
447         setNonDirtyValue(value);
448 }
449
450 int HTMLTextAreaElement::maxLength() const
451 {
452     int value;
453     if (!parseHTMLInteger(getAttribute(maxlengthAttr), value))
454         return -1;
455     return value >= 0 ? value : -1;
456 }
457
458 int HTMLTextAreaElement::minLength() const
459 {
460     int value;
461     if (!parseHTMLInteger(getAttribute(minlengthAttr), value))
462         return -1;
463     return value >= 0 ? value : -1;
464 }
465
466 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionState& exceptionState)
467 {
468     int min = minLength();
469     if (newValue < 0)
470         exceptionState.throwDOMException(IndexSizeError, "The value provided (" + String::number(newValue) + ") is not positive or 0.");
471     else if (min >= 0 && newValue < min)
472         exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMinimumBound("maxLength", newValue, min));
473     else
474         setIntegralAttribute(maxlengthAttr, newValue);
475 }
476
477 void HTMLTextAreaElement::setMinLength(int newValue, ExceptionState& exceptionState)
478 {
479     int max = maxLength();
480     if (newValue < 0)
481         exceptionState.throwDOMException(IndexSizeError, "The value provided (" + String::number(newValue) + ") is not positive or 0.");
482     else if (max >= 0 && newValue > max)
483         exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMaximumBound("minLength", newValue, max));
484     else
485         setIntegralAttribute(minlengthAttr, newValue);
486 }
487
488 String HTMLTextAreaElement::suggestedValue() const
489 {
490     return m_suggestedValue;
491 }
492
493 void HTMLTextAreaElement::setSuggestedValue(const String& value)
494 {
495     m_suggestedValue = value;
496
497     if (!value.isNull())
498         setInnerEditorValue(m_suggestedValue);
499     else
500         setInnerEditorValue(m_value);
501     updatePlaceholderVisibility(false);
502     setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::ControlValue));
503 }
504
505 String HTMLTextAreaElement::validationMessage() const
506 {
507     if (!willValidate())
508         return String();
509
510     if (customError())
511         return customValidationMessage();
512
513     if (valueMissing())
514         return locale().queryString(blink::WebLocalizedString::ValidationValueMissing);
515
516     if (tooLong())
517         return locale().validationMessageTooLongText(computeLengthForSubmission(value()), maxLength());
518
519     if (tooShort())
520         return locale().validationMessageTooShortText(computeLengthForSubmission(value()), minLength());
521
522     return String();
523 }
524
525 bool HTMLTextAreaElement::valueMissing() const
526 {
527     // We should not call value() for performance.
528     return willValidate() && valueMissing(0);
529 }
530
531 bool HTMLTextAreaElement::valueMissing(const String* value) const
532 {
533     return isRequiredFormControl() && !isDisabledOrReadOnly() && (value ? *value : this->value()).isEmpty();
534 }
535
536 bool HTMLTextAreaElement::tooLong() const
537 {
538     // We should not call value() for performance.
539     return willValidate() && tooLong(0, CheckDirtyFlag);
540 }
541
542 bool HTMLTextAreaElement::tooShort() const
543 {
544     // We should not call value() for performance.
545     return willValidate() && tooShort(0, CheckDirtyFlag);
546 }
547
548 bool HTMLTextAreaElement::tooLong(const String* value, NeedsToCheckDirtyFlag check) const
549 {
550     // Return false for the default value or value set by script even if it is
551     // longer than maxLength.
552     if (check == CheckDirtyFlag && !lastChangeWasUserEdit())
553         return false;
554
555     int max = maxLength();
556     if (max < 0)
557         return false;
558     return computeLengthForSubmission(value ? *value : this->value()) > static_cast<unsigned>(max);
559 }
560
561 bool HTMLTextAreaElement::tooShort(const String* value, NeedsToCheckDirtyFlag check) const
562 {
563     // Return false for the default value or value set by script even if it is
564     // shorter than minLength.
565     if (check == CheckDirtyFlag && !lastChangeWasUserEdit())
566         return false;
567
568     int min = minLength();
569     if (min <= 0)
570         return false;
571     // An empty string is excluded from minlength check.
572     unsigned len = computeLengthForSubmission(value ? *value : this->value());
573     return len > 0 && len < static_cast<unsigned>(min);
574 }
575
576 bool HTMLTextAreaElement::isValidValue(const String& candidate) const
577 {
578     return !valueMissing(&candidate) && !tooLong(&candidate, IgnoreDirtyFlag) && !tooShort(&candidate, IgnoreDirtyFlag);
579 }
580
581 void HTMLTextAreaElement::accessKeyAction(bool)
582 {
583     focus();
584 }
585
586 void HTMLTextAreaElement::setCols(int cols)
587 {
588     setIntegralAttribute(colsAttr, cols);
589 }
590
591 void HTMLTextAreaElement::setRows(int rows)
592 {
593     setIntegralAttribute(rowsAttr, rows);
594 }
595
596 bool HTMLTextAreaElement::matchesReadOnlyPseudoClass() const
597 {
598     return isReadOnly();
599 }
600
601 bool HTMLTextAreaElement::matchesReadWritePseudoClass() const
602 {
603     return !isReadOnly();
604 }
605
606 void HTMLTextAreaElement::updatePlaceholderText()
607 {
608     HTMLElement* placeholder = placeholderElement();
609     const AtomicString& placeholderText = fastGetAttribute(placeholderAttr);
610     if (placeholderText.isEmpty()) {
611         if (placeholder)
612             userAgentShadowRoot()->removeChild(placeholder);
613         return;
614     }
615     if (!placeholder) {
616         RefPtrWillBeRawPtr<HTMLDivElement> newElement = HTMLDivElement::create(document());
617         placeholder = newElement.get();
618         placeholder->setShadowPseudoId(AtomicString("-webkit-input-placeholder", AtomicString::ConstructFromLiteral));
619         placeholder->setAttribute(idAttr, ShadowElementNames::placeholder());
620         userAgentShadowRoot()->insertBefore(placeholder, innerEditorElement()->nextSibling());
621     }
622     placeholder->setTextContent(placeholderText);
623 }
624
625 bool HTMLTextAreaElement::isInteractiveContent() const
626 {
627     return true;
628 }
629
630 bool HTMLTextAreaElement::supportsAutofocus() const
631 {
632     return true;
633 }
634
635 }