Home | History | Annotate | Download | only in html
      1 /*
      2  * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org)
      3  *           (C) 1999 Antti Koivisto (koivisto (at) kde.org)
      4  *           (C) 2001 Dirk Mueller (mueller (at) kde.org)
      5  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2010 Apple Inc. All rights reserved.
      6  *           (C) 2006 Alexey Proskuryakov (ap (at) nypop.com)
      7  * Copyright (C) 2007 Samuel Weinig (sam (at) 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 }
     89 
     90 PassRefPtrWillBeRawPtr<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 
     97 void HTMLTextAreaElement::didAddUserAgentShadowRoot(ShadowRoot& root)
     98 {
     99     root.appendChild(TextControlInnerEditorElement::create(document()));
    100 }
    101 
    102 const AtomicString& HTMLTextAreaElement::formControlType() const
    103 {
    104     DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea", AtomicString::ConstructFromLiteral));
    105     return textarea;
    106 }
    107 
    108 FormControlState HTMLTextAreaElement::saveFormControlState() const
    109 {
    110     return m_isDirty ? FormControlState(value()) : FormControlState();
    111 }
    112 
    113 void HTMLTextAreaElement::restoreFormControlState(const FormControlState& state)
    114 {
    115     setValue(state[0]);
    116 }
    117 
    118 void 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 
    128 bool 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 
    141 void 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 
    155 void 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 
    198 RenderObject* HTMLTextAreaElement::createRenderer(RenderStyle*)
    199 {
    200     return new RenderTextControlMultiLine(this);
    201 }
    202 
    203 bool 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 
    219 void HTMLTextAreaElement::resetImpl()
    220 {
    221     setNonDirtyValue(defaultValue());
    222 }
    223 
    224 bool HTMLTextAreaElement::hasCustomFocusLogic() const
    225 {
    226     return true;
    227 }
    228 
    229 bool 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 
    235 bool HTMLTextAreaElement::shouldShowFocusRingOnMouseFocus() const
    236 {
    237     return true;
    238 }
    239 
    240 void 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 
    251 void 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 
    261 void HTMLTextAreaElement::handleFocusEvent(Element*, FocusType)
    262 {
    263     if (LocalFrame* frame = document().frame())
    264         frame->spellChecker().didBeginEditing(this);
    265 }
    266 
    267 void 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 
    284 void 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 
    315 String 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 
    322 void 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 
    334 String HTMLTextAreaElement::value() const
    335 {
    336     updateValue();
    337     return m_value;
    338 }
    339 
    340 void 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 
    349 void HTMLTextAreaElement::setNonDirtyValue(const String& value)
    350 {
    351     setValueCommon(value, DispatchNoEvent, SetSeletion);
    352     m_isDirty = false;
    353 }
    354 
    355 void 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 
    403 void HTMLTextAreaElement::setInnerEditorValue(const String& value)
    404 {
    405     HTMLTextFormControlElement::setInnerEditorValue(value);
    406     m_valueIsUpToDate = true;
    407 }
    408 
    409 String 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 
    422 void 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 
    447 int HTMLTextAreaElement::maxLength() const
    448 {
    449     bool ok;
    450     int value = getAttribute(maxlengthAttr).toInt(&ok);
    451     return ok && value >= 0 ? value : -1;
    452 }
    453 
    454 void 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 
    462 String HTMLTextAreaElement::suggestedValue() const
    463 {
    464     return m_suggestedValue;
    465 }
    466 
    467 void 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 
    479 String 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 
    496 bool HTMLTextAreaElement::valueMissing() const
    497 {
    498     // We should not call value() for performance.
    499     return willValidate() && valueMissing(0);
    500 }
    501 
    502 bool HTMLTextAreaElement::valueMissing(const String* value) const
    503 {
    504     return isRequiredFormControl() && !isDisabledOrReadOnly() && (value ? *value : this->value()).isEmpty();
    505 }
    506 
    507 bool HTMLTextAreaElement::tooLong() const
    508 {
    509     // We should not call value() for performance.
    510     return willValidate() && tooLong(0, CheckDirtyFlag);
    511 }
    512 
    513 bool 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 
    526 bool HTMLTextAreaElement::isValidValue(const String& candidate) const
    527 {
    528     return !valueMissing(&candidate) && !tooLong(&candidate, IgnoreDirtyFlag);
    529 }
    530 
    531 void HTMLTextAreaElement::accessKeyAction(bool)
    532 {
    533     focus();
    534 }
    535 
    536 void HTMLTextAreaElement::setCols(int cols)
    537 {
    538     setIntegralAttribute(colsAttr, cols);
    539 }
    540 
    541 void HTMLTextAreaElement::setRows(int rows)
    542 {
    543     setIntegralAttribute(rowsAttr, rows);
    544 }
    545 
    546 bool HTMLTextAreaElement::matchesReadOnlyPseudoClass() const
    547 {
    548     return isReadOnly();
    549 }
    550 
    551 bool HTMLTextAreaElement::matchesReadWritePseudoClass() const
    552 {
    553     return !isReadOnly();
    554 }
    555 
    556 void 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 
    575 bool HTMLTextAreaElement::isInteractiveContent() const
    576 {
    577     return true;
    578 }
    579 
    580 bool HTMLTextAreaElement::supportsAutofocus() const
    581 {
    582     return true;
    583 }
    584 
    585 }
    586