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