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