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/v8/ExceptionState.h"
     30 #include "bindings/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 WebCore {
     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(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
    120 {
    121     HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
    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 
    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 = focused() ? computeLengthForSubmission(plainText(document().frame()->selection().selection().toNormalizedRange().get())) : 0;
    304     ASSERT(currentLength >= selectionLength);
    305     unsigned baseLength = currentLength - selectionLength;
    306     unsigned appendableLength = unsignedMaxLength > baseLength ? unsignedMaxLength - baseLength : 0;
    307     event->setText(sanitizeUserInputValue(event->text(), appendableLength));
    308 }
    309 
    310 String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength)
    311 {
    312     if (maxLength > 0 && U16_IS_LEAD(proposedValue[maxLength - 1]))
    313         --maxLength;
    314     return proposedValue.left(maxLength);
    315 }
    316 
    317 void HTMLTextAreaElement::updateValue() const
    318 {
    319     if (m_valueIsUpToDate)
    320         return;
    321 
    322     ASSERT(renderer());
    323     m_value = innerEditorValue();
    324     const_cast<HTMLTextAreaElement*>(this)->m_valueIsUpToDate = true;
    325     const_cast<HTMLTextAreaElement*>(this)->notifyFormStateChanged();
    326     m_isDirty = true;
    327     const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility(false);
    328 }
    329 
    330 String HTMLTextAreaElement::value() const
    331 {
    332     updateValue();
    333     return m_value;
    334 }
    335 
    336 void HTMLTextAreaElement::setValue(const String& value, TextFieldEventBehavior eventBehavior)
    337 {
    338     RefPtrWillBeRawPtr<HTMLTextAreaElement> protector(this);
    339     setValueCommon(value, eventBehavior);
    340     m_isDirty = true;
    341     setNeedsValidityCheck();
    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);
    349     m_isDirty = false;
    350     setNeedsValidityCheck();
    351 }
    352 
    353 void HTMLTextAreaElement::setValueCommon(const String& newValue, TextFieldEventBehavior eventBehavior)
    354 {
    355     // Code elsewhere normalizes line endings added by the user via the keyboard or pasting.
    356     // We normalize line endings coming from JavaScript here.
    357     String normalizedValue = newValue.isNull() ? "" : newValue;
    358     normalizedValue.replace("\r\n", "\n");
    359     normalizedValue.replace('\r', '\n');
    360 
    361     // Return early because we don't want to move the caret or trigger other side effects
    362     // when the value isn't changing. This matches Firefox behavior, at least.
    363     if (normalizedValue == value())
    364         return;
    365 
    366     m_value = normalizedValue;
    367     setInnerEditorValue(m_value);
    368     if (eventBehavior == DispatchNoEvent)
    369         setLastChangeWasNotUserEdit();
    370     updatePlaceholderVisibility(false);
    371     setNeedsStyleRecalc(SubtreeStyleChange);
    372     m_suggestedValue = String();
    373 
    374     // Set the caret to the end of the text value.
    375     if (document().focusedElement() == this) {
    376         unsigned endOfString = m_value.length();
    377         setSelectionRange(endOfString, endOfString);
    378     }
    379 
    380     notifyFormStateChanged();
    381     if (eventBehavior == DispatchNoEvent) {
    382         setTextAsOfLastFormControlChangeEvent(normalizedValue);
    383     } else {
    384         if (eventBehavior == DispatchInputAndChangeEvent)
    385             dispatchFormControlInputEvent();
    386         dispatchFormControlChangeEvent();
    387     }
    388 }
    389 
    390 void HTMLTextAreaElement::setInnerEditorValue(const String& value)
    391 {
    392     HTMLTextFormControlElement::setInnerEditorValue(value);
    393     m_valueIsUpToDate = true;
    394 }
    395 
    396 String HTMLTextAreaElement::defaultValue() const
    397 {
    398     StringBuilder value;
    399 
    400     // Since there may be comments, ignore nodes other than text nodes.
    401     for (Node* n = firstChild(); n; n = n->nextSibling()) {
    402         if (n->isTextNode())
    403             value.append(toText(n)->data());
    404     }
    405 
    406     return value.toString();
    407 }
    408 
    409 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
    410 {
    411     RefPtrWillBeRawPtr<Node> protectFromMutationEvents(this);
    412 
    413     // To preserve comments, remove only the text nodes, then add a single text node.
    414     WillBeHeapVector<RefPtrWillBeMember<Node> > textNodes;
    415     for (Node* n = firstChild(); n; n = n->nextSibling()) {
    416         if (n->isTextNode())
    417             textNodes.append(n);
    418     }
    419     size_t size = textNodes.size();
    420     for (size_t i = 0; i < size; ++i)
    421         removeChild(textNodes[i].get(), IGNORE_EXCEPTION);
    422 
    423     // Normalize line endings.
    424     String value = defaultValue;
    425     value.replace("\r\n", "\n");
    426     value.replace('\r', '\n');
    427 
    428     insertBefore(document().createTextNode(value), firstChild(), IGNORE_EXCEPTION);
    429 
    430     if (!m_isDirty)
    431         setNonDirtyValue(value);
    432 }
    433 
    434 int HTMLTextAreaElement::maxLength() const
    435 {
    436     bool ok;
    437     int value = getAttribute(maxlengthAttr).string().toInt(&ok);
    438     return ok && value >= 0 ? value : -1;
    439 }
    440 
    441 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionState& exceptionState)
    442 {
    443     if (newValue < 0)
    444         exceptionState.throwDOMException(IndexSizeError, "The value provided (" + String::number(newValue) + ") is not positive or 0.");
    445     else
    446         setIntegralAttribute(maxlengthAttr, newValue);
    447 }
    448 
    449 String HTMLTextAreaElement::suggestedValue() const
    450 {
    451     return m_suggestedValue;
    452 }
    453 
    454 void HTMLTextAreaElement::setSuggestedValue(const String& value)
    455 {
    456     m_suggestedValue = value;
    457 
    458     if (!value.isNull())
    459         setInnerEditorValue(m_suggestedValue);
    460     else
    461         setInnerEditorValue(m_value);
    462     updatePlaceholderVisibility(false);
    463     setNeedsStyleRecalc(SubtreeStyleChange);
    464 }
    465 
    466 String HTMLTextAreaElement::validationMessage() const
    467 {
    468     if (!willValidate())
    469         return String();
    470 
    471     if (customError())
    472         return customValidationMessage();
    473 
    474     if (valueMissing())
    475         return locale().queryString(blink::WebLocalizedString::ValidationValueMissing);
    476 
    477     if (tooLong())
    478         return locale().validationMessageTooLongText(computeLengthForSubmission(value()), maxLength());
    479 
    480     return String();
    481 }
    482 
    483 bool HTMLTextAreaElement::valueMissing() const
    484 {
    485     return willValidate() && valueMissing(value());
    486 }
    487 
    488 bool HTMLTextAreaElement::tooLong() const
    489 {
    490     return willValidate() && tooLong(value(), CheckDirtyFlag);
    491 }
    492 
    493 bool HTMLTextAreaElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const
    494 {
    495     // Return false for the default value or value set by script even if it is
    496     // longer than maxLength.
    497     if (check == CheckDirtyFlag && !lastChangeWasUserEdit())
    498         return false;
    499 
    500     int max = maxLength();
    501     if (max < 0)
    502         return false;
    503     return computeLengthForSubmission(value) > static_cast<unsigned>(max);
    504 }
    505 
    506 bool HTMLTextAreaElement::isValidValue(const String& candidate) const
    507 {
    508     return !valueMissing(candidate) && !tooLong(candidate, IgnoreDirtyFlag);
    509 }
    510 
    511 void HTMLTextAreaElement::accessKeyAction(bool)
    512 {
    513     focus();
    514 }
    515 
    516 void HTMLTextAreaElement::setCols(int cols)
    517 {
    518     setIntegralAttribute(colsAttr, cols);
    519 }
    520 
    521 void HTMLTextAreaElement::setRows(int rows)
    522 {
    523     setIntegralAttribute(rowsAttr, rows);
    524 }
    525 
    526 bool HTMLTextAreaElement::shouldUseInputMethod()
    527 {
    528     return true;
    529 }
    530 
    531 bool HTMLTextAreaElement::matchesReadOnlyPseudoClass() const
    532 {
    533     return isReadOnly();
    534 }
    535 
    536 bool HTMLTextAreaElement::matchesReadWritePseudoClass() const
    537 {
    538     return !isReadOnly();
    539 }
    540 
    541 void HTMLTextAreaElement::updatePlaceholderText()
    542 {
    543     HTMLElement* placeholder = placeholderElement();
    544     const AtomicString& placeholderText = fastGetAttribute(placeholderAttr);
    545     if (placeholderText.isEmpty()) {
    546         if (placeholder)
    547             userAgentShadowRoot()->removeChild(placeholder);
    548         return;
    549     }
    550     if (!placeholder) {
    551         RefPtrWillBeRawPtr<HTMLDivElement> newElement = HTMLDivElement::create(document());
    552         placeholder = newElement.get();
    553         placeholder->setShadowPseudoId(AtomicString("-webkit-input-placeholder", AtomicString::ConstructFromLiteral));
    554         placeholder->setAttribute(idAttr, ShadowElementNames::placeholder());
    555         userAgentShadowRoot()->insertBefore(placeholder, innerEditorElement()->nextSibling());
    556     }
    557     placeholder->setTextContent(placeholderText);
    558 }
    559 
    560 bool HTMLTextAreaElement::isInteractiveContent() const
    561 {
    562     return true;
    563 }
    564 
    565 bool HTMLTextAreaElement::supportsAutofocus() const
    566 {
    567     return true;
    568 }
    569 
    570 }
    571