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/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/events/ThreadLocalEventNames.h"
     43 #include "core/html/FormDataList.h"
     44 #include "core/html/forms/FormController.h"
     45 #include "core/html/shadow/ShadowElementNames.h"
     46 #include "core/html/shadow/TextControlInnerElements.h"
     47 #include "core/frame/Frame.h"
     48 #include "core/rendering/RenderTextControlMultiLine.h"
     49 #include "platform/text/PlatformLocale.h"
     50 #include "wtf/StdLibExtras.h"
     51 #include "wtf/text/StringBuilder.h"
     52 
     53 namespace WebCore {
     54 
     55 using namespace HTMLNames;
     56 
     57 static const int defaultRows = 2;
     58 static const int defaultCols = 20;
     59 
     60 // On submission, LF characters are converted into CRLF.
     61 // This function returns number of characters considering this.
     62 static unsigned numberOfLineBreaks(const String& text)
     63 {
     64     unsigned length = text.length();
     65     unsigned count = 0;
     66     for (unsigned i = 0; i < length; i++) {
     67         if (text[i] == '\n')
     68             count++;
     69     }
     70     return count;
     71 }
     72 
     73 static inline unsigned computeLengthForSubmission(const String& text)
     74 {
     75     return text.length() + numberOfLineBreaks(text);
     76 }
     77 
     78 HTMLTextAreaElement::HTMLTextAreaElement(Document& document, HTMLFormElement* form)
     79     : HTMLTextFormControlElement(textareaTag, document, form)
     80     , m_rows(defaultRows)
     81     , m_cols(defaultCols)
     82     , m_wrap(SoftWrap)
     83     , m_isDirty(false)
     84 {
     85     setFormControlValueMatchesRenderer(true);
     86     ScriptWrappable::init(this);
     87 }
     88 
     89 PassRefPtr<HTMLTextAreaElement> HTMLTextAreaElement::create(Document& document, HTMLFormElement* form)
     90 {
     91     RefPtr<HTMLTextAreaElement> textArea = adoptRef(new HTMLTextAreaElement(document, form));
     92     textArea->ensureUserAgentShadowRoot();
     93     return textArea.release();
     94 }
     95 
     96 void HTMLTextAreaElement::didAddUserAgentShadowRoot(ShadowRoot& root)
     97 {
     98     root.appendChild(TextControlInnerTextElement::create(document()));
     99 }
    100 
    101 const AtomicString& HTMLTextAreaElement::formControlType() const
    102 {
    103     DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea", AtomicString::ConstructFromLiteral));
    104     return textarea;
    105 }
    106 
    107 FormControlState HTMLTextAreaElement::saveFormControlState() const
    108 {
    109     return m_isDirty ? FormControlState(value()) : FormControlState();
    110 }
    111 
    112 void HTMLTextAreaElement::restoreFormControlState(const FormControlState& state)
    113 {
    114     setValue(state[0]);
    115 }
    116 
    117 void HTMLTextAreaElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
    118 {
    119     HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
    120     setLastChangeWasNotUserEdit();
    121     if (m_isDirty)
    122         setInnerTextValue(value());
    123     else
    124         setNonDirtyValue(defaultValue());
    125 }
    126 
    127 bool HTMLTextAreaElement::isPresentationAttribute(const QualifiedName& name) const
    128 {
    129     if (name == alignAttr) {
    130         // Don't map 'align' attribute.  This matches what Firefox, Opera and IE do.
    131         // See http://bugs.webkit.org/show_bug.cgi?id=7075
    132         return false;
    133     }
    134 
    135     if (name == wrapAttr)
    136         return true;
    137     return HTMLTextFormControlElement::isPresentationAttribute(name);
    138 }
    139 
    140 void HTMLTextAreaElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style)
    141 {
    142     if (name == wrapAttr) {
    143         if (shouldWrapText()) {
    144             addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePreWrap);
    145             addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueBreakWord);
    146         } else {
    147             addPropertyToPresentationAttributeStyle(style, CSSPropertyWhiteSpace, CSSValuePre);
    148             addPropertyToPresentationAttributeStyle(style, CSSPropertyWordWrap, CSSValueNormal);
    149         }
    150     } else
    151         HTMLTextFormControlElement::collectStyleForPresentationAttribute(name, value, style);
    152 }
    153 
    154 void HTMLTextAreaElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
    155 {
    156     if (name == rowsAttr) {
    157         int rows = value.toInt();
    158         if (rows <= 0)
    159             rows = defaultRows;
    160         if (m_rows != rows) {
    161             m_rows = rows;
    162             if (renderer())
    163                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
    164         }
    165     } else if (name == colsAttr) {
    166         int cols = value.toInt();
    167         if (cols <= 0)
    168             cols = defaultCols;
    169         if (m_cols != cols) {
    170             m_cols = cols;
    171             if (renderer())
    172                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
    173         }
    174     } else if (name == wrapAttr) {
    175         // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated.
    176         // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4.
    177         WrapMethod wrap;
    178         if (equalIgnoringCase(value, "physical") || equalIgnoringCase(value, "hard") || equalIgnoringCase(value, "on"))
    179             wrap = HardWrap;
    180         else if (equalIgnoringCase(value, "off"))
    181             wrap = NoWrap;
    182         else
    183             wrap = SoftWrap;
    184         if (wrap != m_wrap) {
    185             m_wrap = wrap;
    186             if (renderer())
    187                 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
    188         }
    189     } else if (name == accesskeyAttr) {
    190         // ignore for the moment
    191     } else if (name == maxlengthAttr)
    192         setNeedsValidityCheck();
    193     else
    194         HTMLTextFormControlElement::parseAttribute(name, value);
    195 }
    196 
    197 RenderObject* HTMLTextAreaElement::createRenderer(RenderStyle*)
    198 {
    199     return new RenderTextControlMultiLine(this);
    200 }
    201 
    202 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool)
    203 {
    204     if (name().isEmpty())
    205         return false;
    206 
    207     document().updateLayout();
    208 
    209     const String& text = (m_wrap == HardWrap) ? valueWithHardLineBreaks() : value();
    210     encoding.appendData(name(), text);
    211 
    212     const AtomicString& dirnameAttrValue = fastGetAttribute(dirnameAttr);
    213     if (!dirnameAttrValue.isNull())
    214         encoding.appendData(dirnameAttrValue, directionForFormData());
    215     return true;
    216 }
    217 
    218 void HTMLTextAreaElement::resetImpl()
    219 {
    220     setNonDirtyValue(defaultValue());
    221 }
    222 
    223 bool HTMLTextAreaElement::hasCustomFocusLogic() const
    224 {
    225     return true;
    226 }
    227 
    228 bool HTMLTextAreaElement::isKeyboardFocusable() const
    229 {
    230     // If a given text area can be focused at all, then it will always be keyboard focusable.
    231     return isFocusable();
    232 }
    233 
    234 bool HTMLTextAreaElement::shouldShowFocusRingOnMouseFocus() const
    235 {
    236     return true;
    237 }
    238 
    239 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection)
    240 {
    241     if (!restorePreviousSelection || !hasCachedSelection()) {
    242         // If this is the first focus, set a caret at the beginning of the text.
    243         // This matches some browsers' behavior; see bug 11746 Comment #15.
    244         // http://bugs.webkit.org/show_bug.cgi?id=11746#c15
    245         setSelectionRange(0, 0);
    246     } else
    247         restoreCachedSelection();
    248 
    249     if (document().frame())
    250         document().frame()->selection().revealSelection();
    251 }
    252 
    253 void HTMLTextAreaElement::defaultEventHandler(Event* event)
    254 {
    255     if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(EventNames::WheelEvent) || event->type() == EventTypeNames::blur))
    256         forwardEvent(event);
    257     else if (renderer() && event->isBeforeTextInsertedEvent())
    258         handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event));
    259 
    260     HTMLTextFormControlElement::defaultEventHandler(event);
    261 }
    262 
    263 void HTMLTextAreaElement::handleFocusEvent(Element*, FocusDirection)
    264 {
    265     if (Frame* frame = document().frame())
    266         frame->spellChecker().didBeginEditing(this);
    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 void HTMLTextAreaElement::rendererWillBeDestroyed()
    316 {
    317     updateValue();
    318 }
    319 
    320 void HTMLTextAreaElement::updateValue() const
    321 {
    322     if (formControlValueMatchesRenderer())
    323         return;
    324 
    325     ASSERT(renderer());
    326     m_value = innerTextValue();
    327     const_cast<HTMLTextAreaElement*>(this)->setFormControlValueMatchesRenderer(true);
    328     const_cast<HTMLTextAreaElement*>(this)->notifyFormStateChanged();
    329     m_isDirty = true;
    330     const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility(false);
    331 }
    332 
    333 String HTMLTextAreaElement::value() const
    334 {
    335     updateValue();
    336     return m_value;
    337 }
    338 
    339 void HTMLTextAreaElement::setValue(const String& value)
    340 {
    341     setValueCommon(value);
    342     m_isDirty = true;
    343     setNeedsValidityCheck();
    344 }
    345 
    346 void HTMLTextAreaElement::setNonDirtyValue(const String& value)
    347 {
    348     setValueCommon(value);
    349     m_isDirty = false;
    350     setNeedsValidityCheck();
    351 }
    352 
    353 void HTMLTextAreaElement::setValueCommon(const String& newValue)
    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     setInnerTextValue(m_value);
    368     setLastChangeWasNotUserEdit();
    369     updatePlaceholderVisibility(false);
    370     setNeedsStyleRecalc();
    371     setFormControlValueMatchesRenderer(true);
    372 
    373     // Set the caret to the end of the text value.
    374     if (document().focusedElement() == this) {
    375         unsigned endOfString = m_value.length();
    376         setSelectionRange(endOfString, endOfString);
    377     }
    378 
    379     notifyFormStateChanged();
    380     setTextAsOfLastFormControlChangeEvent(normalizedValue);
    381 }
    382 
    383 String HTMLTextAreaElement::defaultValue() const
    384 {
    385     StringBuilder value;
    386 
    387     // Since there may be comments, ignore nodes other than text nodes.
    388     for (Node* n = firstChild(); n; n = n->nextSibling()) {
    389         if (n->isTextNode())
    390             value.append(toText(n)->data());
    391     }
    392 
    393     return value.toString();
    394 }
    395 
    396 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
    397 {
    398     RefPtr<Node> protectFromMutationEvents(this);
    399 
    400     // To preserve comments, remove only the text nodes, then add a single text node.
    401     Vector<RefPtr<Node> > textNodes;
    402     for (Node* n = firstChild(); n; n = n->nextSibling()) {
    403         if (n->isTextNode())
    404             textNodes.append(n);
    405     }
    406     size_t size = textNodes.size();
    407     for (size_t i = 0; i < size; ++i)
    408         removeChild(textNodes[i].get(), IGNORE_EXCEPTION);
    409 
    410     // Normalize line endings.
    411     String value = defaultValue;
    412     value.replace("\r\n", "\n");
    413     value.replace('\r', '\n');
    414 
    415     insertBefore(document().createTextNode(value), firstChild(), IGNORE_EXCEPTION);
    416 
    417     if (!m_isDirty)
    418         setNonDirtyValue(value);
    419 }
    420 
    421 int HTMLTextAreaElement::maxLength() const
    422 {
    423     bool ok;
    424     int value = getAttribute(maxlengthAttr).string().toInt(&ok);
    425     return ok && value >= 0 ? value : -1;
    426 }
    427 
    428 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionState& exceptionState)
    429 {
    430     if (newValue < 0)
    431         exceptionState.throwDOMException(IndexSizeError, "The value provided (" + String::number(newValue) + ") is not positive or 0.");
    432     else
    433         setIntegralAttribute(maxlengthAttr, newValue);
    434 }
    435 
    436 String HTMLTextAreaElement::validationMessage() const
    437 {
    438     if (!willValidate())
    439         return String();
    440 
    441     if (customError())
    442         return customValidationMessage();
    443 
    444     if (valueMissing())
    445         return locale().queryString(blink::WebLocalizedString::ValidationValueMissing);
    446 
    447     if (tooLong())
    448         return locale().validationMessageTooLongText(computeLengthForSubmission(value()), maxLength());
    449 
    450     return String();
    451 }
    452 
    453 bool HTMLTextAreaElement::valueMissing() const
    454 {
    455     return willValidate() && valueMissing(value());
    456 }
    457 
    458 bool HTMLTextAreaElement::tooLong() const
    459 {
    460     return willValidate() && tooLong(value(), CheckDirtyFlag);
    461 }
    462 
    463 bool HTMLTextAreaElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const
    464 {
    465     // Return false for the default value or value set by script even if it is
    466     // longer than maxLength.
    467     if (check == CheckDirtyFlag && !lastChangeWasUserEdit())
    468         return false;
    469 
    470     int max = maxLength();
    471     if (max < 0)
    472         return false;
    473     return computeLengthForSubmission(value) > static_cast<unsigned>(max);
    474 }
    475 
    476 bool HTMLTextAreaElement::isValidValue(const String& candidate) const
    477 {
    478     return !valueMissing(candidate) && !tooLong(candidate, IgnoreDirtyFlag);
    479 }
    480 
    481 void HTMLTextAreaElement::accessKeyAction(bool)
    482 {
    483     focus();
    484 }
    485 
    486 void HTMLTextAreaElement::setCols(int cols)
    487 {
    488     setIntegralAttribute(colsAttr, cols);
    489 }
    490 
    491 void HTMLTextAreaElement::setRows(int rows)
    492 {
    493     setIntegralAttribute(rowsAttr, rows);
    494 }
    495 
    496 bool HTMLTextAreaElement::shouldUseInputMethod()
    497 {
    498     return true;
    499 }
    500 
    501 bool HTMLTextAreaElement::matchesReadOnlyPseudoClass() const
    502 {
    503     return isReadOnly();
    504 }
    505 
    506 bool HTMLTextAreaElement::matchesReadWritePseudoClass() const
    507 {
    508     return !isReadOnly();
    509 }
    510 
    511 void HTMLTextAreaElement::updatePlaceholderText()
    512 {
    513     HTMLElement* placeholder = placeholderElement();
    514     String placeholderText = strippedPlaceholder();
    515     if (placeholderText.isEmpty()) {
    516         if (placeholder)
    517             userAgentShadowRoot()->removeChild(placeholder);
    518         return;
    519     }
    520     if (!placeholder) {
    521         RefPtr<HTMLDivElement> newElement = HTMLDivElement::create(document());
    522         placeholder = newElement.get();
    523         placeholder->setPseudo(AtomicString("-webkit-input-placeholder", AtomicString::ConstructFromLiteral));
    524         placeholder->setAttribute(idAttr, ShadowElementNames::placeholder());
    525         userAgentShadowRoot()->insertBefore(placeholder, innerTextElement()->nextSibling());
    526     }
    527     placeholder->setTextContent(placeholderText);
    528 }
    529 
    530 bool HTMLTextAreaElement::isInteractiveContent() const
    531 {
    532     return true;
    533 }
    534 
    535 }
    536