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 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 "ChromeClient.h"
32 #include "EventNames.h"
33 #include "FocusController.h"
34 #include "FormDataList.h"
36 #include "HTMLNames.h"
37 #include "MappedAttribute.h"
39 #include "RenderStyle.h"
40 #include "RenderTextControlMultiLine.h"
41 #include "ScriptEventListener.h"
43 #include "VisibleSelection.h"
44 #include <wtf/StdLibExtras.h>
48 using namespace HTMLNames;
50 static const int defaultRows = 2;
51 static const int defaultCols = 20;
53 static inline void notifyFormStateChanged(const HTMLTextAreaElement* element)
55 Frame* frame = element->document()->frame();
58 frame->page()->chrome()->client()->formStateDidChange(element);
61 HTMLTextAreaElement::HTMLTextAreaElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
62 : HTMLFormControlElementWithState(tagName, document, form)
66 , m_cachedSelectionStart(-1)
67 , m_cachedSelectionEnd(-1)
69 ASSERT(hasTagName(textareaTag));
70 setFormControlValueMatchesRenderer(true);
71 notifyFormStateChanged(this);
74 const AtomicString& HTMLTextAreaElement::formControlType() const
76 DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea"));
80 bool HTMLTextAreaElement::saveFormControlState(String& result) const
86 void HTMLTextAreaElement::restoreFormControlState(const String& state)
88 setDefaultValue(state);
91 int HTMLTextAreaElement::selectionStart()
95 if (document()->focusedNode() != this && m_cachedSelectionStart >= 0)
96 return m_cachedSelectionStart;
97 return toRenderTextControl(renderer())->selectionStart();
100 int HTMLTextAreaElement::selectionEnd()
104 if (document()->focusedNode() != this && m_cachedSelectionEnd >= 0)
105 return m_cachedSelectionEnd;
106 return toRenderTextControl(renderer())->selectionEnd();
109 void HTMLTextAreaElement::setSelectionStart(int start)
113 toRenderTextControl(renderer())->setSelectionStart(start);
116 void HTMLTextAreaElement::setSelectionEnd(int end)
120 toRenderTextControl(renderer())->setSelectionEnd(end);
123 void HTMLTextAreaElement::select()
127 toRenderTextControl(renderer())->select();
130 void HTMLTextAreaElement::setSelectionRange(int start, int end)
134 toRenderTextControl(renderer())->setSelectionRange(start, end);
137 void HTMLTextAreaElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
139 setValue(defaultValue());
140 HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
143 void HTMLTextAreaElement::parseMappedAttribute(MappedAttribute* attr)
145 if (attr->name() == rowsAttr) {
146 int rows = attr->value().toInt();
149 if (m_rows != rows) {
152 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
154 } else if (attr->name() == colsAttr) {
155 int cols = attr->value().toInt();
158 if (m_cols != cols) {
161 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
163 } else if (attr->name() == wrapAttr) {
164 // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated.
165 // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4.
167 if (equalIgnoringCase(attr->value(), "physical") || equalIgnoringCase(attr->value(), "hard") || equalIgnoringCase(attr->value(), "on"))
169 else if (equalIgnoringCase(attr->value(), "off"))
173 if (wrap != m_wrap) {
176 renderer()->setNeedsLayoutAndPrefWidthsRecalc();
178 } else if (attr->name() == accesskeyAttr) {
179 // ignore for the moment
180 } else if (attr->name() == alignAttr) {
181 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
182 // See http://bugs.webkit.org/show_bug.cgi?id=7075
183 } else if (attr->name() == onfocusAttr)
184 setAttributeEventListener(eventNames().focusEvent, createAttributeEventListener(this, attr));
185 else if (attr->name() == onblurAttr)
186 setAttributeEventListener(eventNames().blurEvent, createAttributeEventListener(this, attr));
187 else if (attr->name() == onselectAttr)
188 setAttributeEventListener(eventNames().selectEvent, createAttributeEventListener(this, attr));
189 else if (attr->name() == onchangeAttr)
190 setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr));
192 HTMLFormControlElementWithState::parseMappedAttribute(attr);
195 RenderObject* HTMLTextAreaElement::createRenderer(RenderArena* arena, RenderStyle*)
197 return new (arena) RenderTextControlMultiLine(this);
200 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool)
202 if (name().isEmpty())
205 // FIXME: It's not acceptable to ignore the HardWrap setting when there is no renderer.
206 // While we have no evidence this has ever been a practical problem, it would be best to fix it some day.
207 RenderTextControl* control = toRenderTextControl(renderer());
208 const String& text = (m_wrap == HardWrap && control) ? control->textWithHardLineBreaks() : value();
209 encoding.appendData(name(), text);
213 void HTMLTextAreaElement::reset()
215 setValue(defaultValue());
218 bool HTMLTextAreaElement::isKeyboardFocusable(KeyboardEvent*) const
220 // If a given text area can be focused at all, then it will always be keyboard focusable.
221 return isFocusable();
224 bool HTMLTextAreaElement::isMouseFocusable() const
226 return isFocusable();
229 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection)
233 if (!restorePreviousSelection || m_cachedSelectionStart < 0) {
234 #if ENABLE(ON_FIRST_TEXTAREA_FOCUS_SELECT_ALL)
235 // Devices with trackballs or d-pads may focus on a textarea in route
236 // to another focusable node. By selecting all text, the next movement
237 // can more readily be interpreted as moving to the next node.
240 // If this is the first focus, set a caret at the beginning of the text.
241 // This matches some browsers' behavior; see bug 11746 Comment #15.
242 // http://bugs.webkit.org/show_bug.cgi?id=11746#c15
243 setSelectionRange(0, 0);
246 // Restore the cached selection. This matches other browsers' behavior.
247 setSelectionRange(m_cachedSelectionStart, m_cachedSelectionEnd);
250 if (document()->frame())
251 document()->frame()->revealSelection();
254 void HTMLTextAreaElement::defaultEventHandler(Event* event)
256 if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->isWheelEvent() || event->type() == eventNames().blurEvent))
257 static_cast<RenderTextControlMultiLine*>(renderer())->forwardEvent(event);
259 HTMLFormControlElementWithState::defaultEventHandler(event);
262 void HTMLTextAreaElement::rendererWillBeDestroyed()
267 void HTMLTextAreaElement::updateValue() const
269 if (formControlValueMatchesRenderer())
273 m_value = toRenderTextControl(renderer())->text();
274 const_cast<HTMLTextAreaElement*>(this)->setFormControlValueMatchesRenderer(true);
275 notifyFormStateChanged(this);
278 String HTMLTextAreaElement::value() const
284 void HTMLTextAreaElement::setValue(const String& value)
286 // Code elsewhere normalizes line endings added by the user via the keyboard or pasting.
287 // We normalize line endings coming from JavaScript here.
288 String normalizedValue = value.isNull() ? "" : value;
289 normalizedValue.replace("\r\n", "\n");
290 normalizedValue.replace('\r', '\n');
292 // Return early because we don't want to move the caret or trigger other side effects
293 // when the value isn't changing. This matches Firefox behavior, at least.
294 if (normalizedValue == this->value())
297 m_value = normalizedValue;
298 setFormControlValueMatchesRenderer(true);
300 document()->updateStyleIfNeeded();
302 renderer()->updateFromElement();
304 // Set the caret to the end of the text value.
305 if (document()->focusedNode() == this) {
306 unsigned endOfString = m_value.length();
307 setSelectionRange(endOfString, endOfString);
310 setNeedsStyleRecalc();
311 notifyFormStateChanged(this);
314 String HTMLTextAreaElement::defaultValue() const
318 // Since there may be comments, ignore nodes other than text nodes.
319 for (Node* n = firstChild(); n; n = n->nextSibling()) {
321 value += static_cast<Text*>(n)->data();
324 UChar firstCharacter = value[0];
325 if (firstCharacter == '\r' && value[1] == '\n')
327 else if (firstCharacter == '\r' || firstCharacter == '\n')
333 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue)
335 // To preserve comments, remove only the text nodes, then add a single text node.
337 Vector<RefPtr<Node> > textNodes;
338 for (Node* n = firstChild(); n; n = n->nextSibling()) {
343 size_t size = textNodes.size();
344 for (size_t i = 0; i < size; ++i)
345 removeChild(textNodes[i].get(), ec);
347 // Normalize line endings.
348 // Add an extra line break if the string starts with one, since
349 // the code to read default values from the DOM strips the leading one.
350 String value = defaultValue;
351 value.replace("\r\n", "\n");
352 value.replace('\r', '\n');
353 if (value[0] == '\n')
354 value = "\n" + value;
356 insertBefore(document()->createTextNode(value), firstChild(), ec);
361 void HTMLTextAreaElement::accessKeyAction(bool)
366 const AtomicString& HTMLTextAreaElement::accessKey() const
368 return getAttribute(accesskeyAttr);
371 void HTMLTextAreaElement::setAccessKey(const String& value)
373 setAttribute(accesskeyAttr, value);
376 void HTMLTextAreaElement::setCols(int cols)
378 setAttribute(colsAttr, String::number(cols));
381 void HTMLTextAreaElement::setRows(int rows)
383 setAttribute(rowsAttr, String::number(rows));
386 VisibleSelection HTMLTextAreaElement::selection() const
388 if (!renderer() || m_cachedSelectionStart < 0 || m_cachedSelectionEnd < 0)
389 return VisibleSelection();
390 return toRenderTextControl(renderer())->selection(m_cachedSelectionStart, m_cachedSelectionEnd);
393 bool HTMLTextAreaElement::shouldUseInputMethod() const