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)
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.
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.
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.
27 #include "HTMLTextAreaElement.h"
29 #include "Attribute.h"
30 #include "BeforeTextInsertedEvent.h"
31 #include "CSSValueKeywords.h"
34 #include "EventNames.h"
35 #include "ExceptionCode.h"
36 #include "FormDataList.h"
38 #include "HTMLNames.h"
39 #include "RenderTextControlMultiLine.h"
40 #include "ShadowRoot.h"
42 #include "TextControlInnerElements.h"
43 #include "TextIterator.h"
44 #include <wtf/StdLibExtras.h>
48 using namespace HTMLNames;
50 static const int defaultRows = 2;
51 static const int defaultCols = 20;
53 HTMLTextAreaElement::HTMLTextAreaElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
54 : HTMLTextFormControlElement(tagName, document, form)
59 , m_wasModifiedByUser(false)
61 ASSERT(hasTagName(textareaTag));
62 setFormControlValueMatchesRenderer(true);
65 PassRefPtr<HTMLTextAreaElement> HTMLTextAreaElement::create(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
67 RefPtr<HTMLTextAreaElement> textArea = adoptRef(new HTMLTextAreaElement(tagName, document, form));
68 textArea->createShadowSubtree();
69 return textArea.release();
72 void HTMLTextAreaElement::createShadowSubtree()
75 ensureShadowRoot()->appendChild(TextControlInnerTextElement::create(document()), ec);
78 const AtomicString& HTMLTextAreaElement::formControlType() const
80 DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea"));
84 bool HTMLTextAreaElement::saveFormControlState(String& result) const
86 String currentValue = value();
87 if (currentValue == defaultValue())
89 result = currentValue;
93 void HTMLTextAreaElement::restoreFormControlState(const String& state)
98 void HTMLTextAreaElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
100 setLastChangeWasNotUserEdit();
102 setNonDirtyValue(defaultValue());
103 setInnerTextValue(value());
104 HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
107 void HTMLTextAreaElement::parseMappedAttribute(Attribute* attr)
109 if (attr->name() == rowsAttr) {
110 int rows = attr->value().toInt();
113 if (m_rows != rows) {
116 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
118 } else if (attr->name() == colsAttr) {
119 int cols = attr->value().toInt();
122 if (m_cols != cols) {
125 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
127 } else if (attr->name() == wrapAttr) {
128 // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated.
129 // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4.
131 if (equalIgnoringCase(attr->value(), "physical") || equalIgnoringCase(attr->value(), "hard") || equalIgnoringCase(attr->value(), "on"))
133 else if (equalIgnoringCase(attr->value(), "off"))
137 if (wrap != m_wrap) {
140 if (shouldWrapText()) {
141 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValuePreWrap);
142 addCSSProperty(attr, CSSPropertyWordWrap, CSSValueBreakWord);
144 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValuePre);
145 addCSSProperty(attr, CSSPropertyWordWrap, CSSValueNormal);
149 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
151 } else if (attr->name() == accesskeyAttr) {
152 // ignore for the moment
153 } else if (attr->name() == alignAttr) {
154 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
155 // See http://bugs.webkit.org/show_bug.cgi?id=7075
156 } else if (attr->name() == maxlengthAttr)
157 setNeedsValidityCheck();
159 HTMLTextFormControlElement::parseMappedAttribute(attr);
162 RenderObject* HTMLTextAreaElement::createRenderer(RenderArena* arena, RenderStyle*)
164 return new (arena) RenderTextControlMultiLine(this);
167 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool)
169 if (name().isEmpty())
172 document()->updateLayout();
174 const String& text = (m_wrap == HardWrap) ? valueWithHardLineBreaks() : value();
175 encoding.appendData(name(), text);
177 const AtomicString& dirnameAttrValue = fastGetAttribute(dirnameAttr);
178 if (!dirnameAttrValue.isNull())
179 encoding.appendData(dirnameAttrValue, directionForFormData());
183 void HTMLTextAreaElement::reset()
185 setNonDirtyValue(defaultValue());
188 bool HTMLTextAreaElement::isKeyboardFocusable(KeyboardEvent*) const
190 // If a given text area can be focused at all, then it will always be keyboard focusable.
191 return isFocusable();
194 bool HTMLTextAreaElement::isMouseFocusable() const
196 return isFocusable();
199 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection)
201 if (!restorePreviousSelection || !hasCachedSelection()) {
202 // If this is the first focus, set a caret at the beginning of the text.
203 // This matches some browsers' behavior; see bug 11746 Comment #15.
204 // http://bugs.webkit.org/show_bug.cgi?id=11746#c15
205 setSelectionRange(0, 0);
207 restoreCachedSelection();
209 if (document()->frame())
210 document()->frame()->selection()->revealSelection();
213 void HTMLTextAreaElement::defaultEventHandler(Event* event)
215 if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->hasInterface(eventNames().interfaceForWheelEvent) || event->type() == eventNames().blurEvent))
217 else if (renderer() && event->isBeforeTextInsertedEvent())
218 handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event));
220 HTMLTextFormControlElement::defaultEventHandler(event);
223 void HTMLTextAreaElement::subtreeHasChanged()
225 setChangedSinceLastFormControlChangeEvent(true);
226 setFormControlValueMatchesRenderer(false);
227 setNeedsValidityCheck();
232 if (Frame* frame = document()->frame())
233 frame->editor()->textDidChangeInTextArea(this);
234 // When typing in a textarea, childrenChanged is not called, so we need to force the directionality check.
235 calculateAndAdjustDirectionality();
238 void HTMLTextAreaElement::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event) const
242 int signedMaxLength = maxLength();
243 if (signedMaxLength < 0)
245 unsigned unsignedMaxLength = static_cast<unsigned>(signedMaxLength);
247 unsigned currentLength = numGraphemeClusters(innerTextValue());
248 // selectionLength represents the selection length of this text field to be
249 // removed by this insertion.
250 // If the text field has no focus, we don't need to take account of the
251 // selection length. The selection is the source of text drag-and-drop in
252 // that case, and nothing in the text field will be removed.
253 unsigned selectionLength = focused() ? numGraphemeClusters(plainText(document()->frame()->selection()->selection().toNormalizedRange().get())) : 0;
254 ASSERT(currentLength >= selectionLength);
255 unsigned baseLength = currentLength - selectionLength;
256 unsigned appendableLength = unsignedMaxLength > baseLength ? unsignedMaxLength - baseLength : 0;
257 event->setText(sanitizeUserInputValue(event->text(), appendableLength));
260 String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength)
262 return proposedValue.left(numCharactersInGraphemeClusters(proposedValue, maxLength));
265 HTMLElement* HTMLTextAreaElement::innerTextElement() const
267 Node* node = shadowRoot()->firstChild();
268 ASSERT(!node || node->hasTagName(divTag));
269 return toHTMLElement(node);
272 void HTMLTextAreaElement::rendererWillBeDestroyed()
277 void HTMLTextAreaElement::updateValue() const
279 if (formControlValueMatchesRenderer())
283 m_value = innerTextValue();
284 const_cast<HTMLTextAreaElement*>(this)->setFormControlValueMatchesRenderer(true);
285 const_cast<HTMLTextAreaElement*>(this)->notifyFormStateChanged();
287 m_wasModifiedByUser = true;
288 const_cast<HTMLTextAreaElement*>(this)->updatePlaceholderVisibility(false);
291 String HTMLTextAreaElement::value() const
297 void HTMLTextAreaElement::setValue(const String& value)
299 setValueCommon(value);
301 setNeedsValidityCheck();
304 void HTMLTextAreaElement::setNonDirtyValue(const String& value)
306 setValueCommon(value);
308 setNeedsValidityCheck();
311 void HTMLTextAreaElement::setValueCommon(const String& newValue)
313 m_wasModifiedByUser = false;
314 // Code elsewhere normalizes line endings added by the user via the keyboard or pasting.
315 // We normalize line endings coming from JavaScript here.
316 String normalizedValue = newValue.isNull() ? "" : newValue;
317 normalizedValue.replace("\r\n", "\n");
318 normalizedValue.replace('\r', '\n');
320 // Return early because we don't want to move the caret or trigger other side effects
321 // when the value isn't changing. This matches Firefox behavior, at least.
322 if (normalizedValue == value())
325 m_value = normalizedValue;
326 setInnerTextValue(m_value);
327 setLastChangeWasNotUserEdit();
328 updatePlaceholderVisibility(false);
329 setNeedsStyleRecalc();
330 setFormControlValueMatchesRenderer(true);
332 // Set the caret to the end of the text value.
333 if (document()->focusedNode() == this) {
334 unsigned endOfString = m_value.length();
335 setSelectionRange(endOfString, endOfString);
338 notifyFormStateChanged();
339 setTextAsOfLastFormControlChangeEvent(normalizedValue);
342 String HTMLTextAreaElement::defaultValue() const
346 // Since there may be comments, ignore nodes other than text nodes.
347 for (Node* n = firstChild(); n; n = n->nextSibling()) {
349 value += static_cast<Text*>(n)->data();
355 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
357 // To preserve comments, remove only the text nodes, then add a single text node.
359 Vector<RefPtr<Node> > textNodes;
360 for (Node* n = firstChild(); n; n = n->nextSibling()) {
365 size_t size = textNodes.size();
366 for (size_t i = 0; i < size; ++i)
367 removeChild(textNodes[i].get(), ec);
369 // Normalize line endings.
370 String value = defaultValue;
371 value.replace("\r\n", "\n");
372 value.replace('\r', '\n');
374 insertBefore(document()->createTextNode(value), firstChild(), ec);
377 setNonDirtyValue(value);
380 int HTMLTextAreaElement::maxLength() const
383 int value = getAttribute(maxlengthAttr).string().toInt(&ok);
384 return ok && value >= 0 ? value : -1;
387 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionCode& ec)
392 setAttribute(maxlengthAttr, String::number(newValue));
395 bool HTMLTextAreaElement::tooLong(const String& value, NeedsToCheckDirtyFlag check) const
397 // Return false for the default value or value set by script even if it is
398 // longer than maxLength.
399 if (check == CheckDirtyFlag && !m_wasModifiedByUser)
402 int max = maxLength();
405 return numGraphemeClusters(value) > static_cast<unsigned>(max);
408 bool HTMLTextAreaElement::isValidValue(const String& candidate) const
410 return !valueMissing(candidate) && !tooLong(candidate, IgnoreDirtyFlag);
413 void HTMLTextAreaElement::accessKeyAction(bool)
418 void HTMLTextAreaElement::setCols(int cols)
420 setAttribute(colsAttr, String::number(cols));
423 void HTMLTextAreaElement::setRows(int rows)
425 setAttribute(rowsAttr, String::number(rows));
428 bool HTMLTextAreaElement::shouldUseInputMethod()
433 HTMLElement* HTMLTextAreaElement::placeholderElement() const
435 return m_placeholder.get();
438 void HTMLTextAreaElement::updatePlaceholderText()
440 ExceptionCode ec = 0;
441 String placeholderText = strippedPlaceholder();
442 if (placeholderText.isEmpty()) {
444 shadowRoot()->removeChild(m_placeholder.get(), ec);
446 m_placeholder.clear();
450 if (!m_placeholder) {
451 m_placeholder = HTMLDivElement::create(document());
452 m_placeholder->setShadowPseudoId("-webkit-input-placeholder");
453 shadowRoot()->insertBefore(m_placeholder, shadowRoot()->firstChild()->nextSibling(), ec);
456 m_placeholder->setInnerText(placeholderText, ec);