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 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 "HTMLTextAreaElement.h" 28 29 #include "BeforeTextInsertedEvent.h" 30 #include "Chrome.h" 31 #include "ChromeClient.h" 32 #include "CSSValueKeywords.h" 33 #include "Document.h" 34 #include "Event.h" 35 #include "EventNames.h" 36 #include "ExceptionCode.h" 37 #include "FocusController.h" 38 #include "FormDataList.h" 39 #include "Frame.h" 40 #include "HTMLNames.h" 41 #include "InputElement.h" 42 #include "MappedAttribute.h" 43 #include "Page.h" 44 #include "RenderStyle.h" 45 #include "RenderTextControlMultiLine.h" 46 #include "ScriptEventListener.h" 47 #include "Text.h" 48 #include "TextIterator.h" 49 #include "VisibleSelection.h" 50 #include <wtf/StdLibExtras.h> 51 52 #ifdef ANDROID_ACCEPT_CHANGES_TO_FOCUSED_TEXTFIELDS 53 #include "WebViewCore.h" 54 #endif 55 56 namespace WebCore { 57 58 using namespace HTMLNames; 59 60 static const int defaultRows = 2; 61 static const int defaultCols = 20; 62 63 static inline void notifyFormStateChanged(const HTMLTextAreaElement* element) 64 { 65 Frame* frame = element->document()->frame(); 66 if (!frame) 67 return; 68 frame->page()->chrome()->client()->formStateDidChange(element); 69 } 70 71 HTMLTextAreaElement::HTMLTextAreaElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form) 72 : HTMLTextFormControlElement(tagName, document, form) 73 , m_rows(defaultRows) 74 , m_cols(defaultCols) 75 , m_wrap(SoftWrap) 76 , m_cachedSelectionStart(-1) 77 , m_cachedSelectionEnd(-1) 78 , m_isDirty(false) 79 { 80 ASSERT(hasTagName(textareaTag)); 81 setFormControlValueMatchesRenderer(true); 82 notifyFormStateChanged(this); 83 } 84 85 const AtomicString& HTMLTextAreaElement::formControlType() const 86 { 87 DEFINE_STATIC_LOCAL(const AtomicString, textarea, ("textarea")); 88 return textarea; 89 } 90 91 bool HTMLTextAreaElement::saveFormControlState(String& result) const 92 { 93 result = value(); 94 return true; 95 } 96 97 void HTMLTextAreaElement::restoreFormControlState(const String& state) 98 { 99 setValue(state); 100 } 101 102 void HTMLTextAreaElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) 103 { 104 setNonDirtyValue(defaultValue()); 105 HTMLElement::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); 106 } 107 108 void HTMLTextAreaElement::parseMappedAttribute(MappedAttribute* attr) 109 { 110 if (attr->name() == rowsAttr) { 111 int rows = attr->value().toInt(); 112 if (rows <= 0) 113 rows = defaultRows; 114 if (m_rows != rows) { 115 m_rows = rows; 116 if (renderer()) 117 renderer()->setNeedsLayoutAndPrefWidthsRecalc(); 118 } 119 } else if (attr->name() == colsAttr) { 120 int cols = attr->value().toInt(); 121 if (cols <= 0) 122 cols = defaultCols; 123 if (m_cols != cols) { 124 m_cols = cols; 125 if (renderer()) 126 renderer()->setNeedsLayoutAndPrefWidthsRecalc(); 127 } 128 } else if (attr->name() == wrapAttr) { 129 // The virtual/physical values were a Netscape extension of HTML 3.0, now deprecated. 130 // The soft/hard /off values are a recommendation for HTML 4 extension by IE and NS 4. 131 WrapMethod wrap; 132 if (equalIgnoringCase(attr->value(), "physical") || equalIgnoringCase(attr->value(), "hard") || equalIgnoringCase(attr->value(), "on")) 133 wrap = HardWrap; 134 else if (equalIgnoringCase(attr->value(), "off")) 135 wrap = NoWrap; 136 else 137 wrap = SoftWrap; 138 if (wrap != m_wrap) { 139 m_wrap = wrap; 140 141 if (shouldWrapText()) { 142 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValuePreWrap); 143 addCSSProperty(attr, CSSPropertyWordWrap, CSSValueBreakWord); 144 } else { 145 addCSSProperty(attr, CSSPropertyWhiteSpace, CSSValuePre); 146 addCSSProperty(attr, CSSPropertyWordWrap, CSSValueNormal); 147 } 148 149 if (renderer()) 150 renderer()->setNeedsLayoutAndPrefWidthsRecalc(); 151 } 152 } else if (attr->name() == accesskeyAttr) { 153 // ignore for the moment 154 } else if (attr->name() == alignAttr) { 155 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do. 156 // See http://bugs.webkit.org/show_bug.cgi?id=7075 157 } else if (attr->name() == maxlengthAttr) 158 setNeedsValidityCheck(); 159 else 160 HTMLTextFormControlElement::parseMappedAttribute(attr); 161 } 162 163 RenderObject* HTMLTextAreaElement::createRenderer(RenderArena* arena, RenderStyle*) 164 { 165 return new (arena) RenderTextControlMultiLine(this, placeholderShouldBeVisible()); 166 } 167 168 bool HTMLTextAreaElement::appendFormData(FormDataList& encoding, bool) 169 { 170 if (name().isEmpty()) 171 return false; 172 173 // FIXME: It's not acceptable to ignore the HardWrap setting when there is no renderer. 174 // While we have no evidence this has ever been a practical problem, it would be best to fix it some day. 175 RenderTextControl* control = toRenderTextControl(renderer()); 176 const String& text = (m_wrap == HardWrap && control) ? control->textWithHardLineBreaks() : value(); 177 encoding.appendData(name(), text); 178 return true; 179 } 180 181 void HTMLTextAreaElement::reset() 182 { 183 setNonDirtyValue(defaultValue()); 184 } 185 186 bool HTMLTextAreaElement::isKeyboardFocusable(KeyboardEvent*) const 187 { 188 // If a given text area can be focused at all, then it will always be keyboard focusable. 189 return isFocusable(); 190 } 191 192 bool HTMLTextAreaElement::isMouseFocusable() const 193 { 194 return isFocusable(); 195 } 196 197 void HTMLTextAreaElement::updateFocusAppearance(bool restorePreviousSelection) 198 { 199 ASSERT(renderer()); 200 ASSERT(!document()->childNeedsAndNotInStyleRecalc()); 201 202 if (!restorePreviousSelection || m_cachedSelectionStart < 0) { 203 #if ENABLE(ON_FIRST_TEXTAREA_FOCUS_SELECT_ALL) 204 // Devices with trackballs or d-pads may focus on a textarea in route 205 // to another focusable node. By selecting all text, the next movement 206 // can more readily be interpreted as moving to the next node. 207 select(); 208 #else 209 // If this is the first focus, set a caret at the beginning of the text. 210 // This matches some browsers' behavior; see bug 11746 Comment #15. 211 // http://bugs.webkit.org/show_bug.cgi?id=11746#c15 212 setSelectionRange(0, 0); 213 #endif 214 } else { 215 // Restore the cached selection. This matches other browsers' behavior. 216 setSelectionRange(m_cachedSelectionStart, m_cachedSelectionEnd); 217 } 218 219 if (document()->frame()) 220 document()->frame()->revealSelection(); 221 } 222 223 void HTMLTextAreaElement::defaultEventHandler(Event* event) 224 { 225 if (renderer() && (event->isMouseEvent() || event->isDragEvent() || event->isWheelEvent() || event->type() == eventNames().blurEvent)) 226 toRenderTextControlMultiLine(renderer())->forwardEvent(event); 227 else if (renderer() && event->isBeforeTextInsertedEvent()) 228 handleBeforeTextInsertedEvent(static_cast<BeforeTextInsertedEvent*>(event)); 229 230 HTMLFormControlElementWithState::defaultEventHandler(event); 231 } 232 233 void HTMLTextAreaElement::handleBeforeTextInsertedEvent(BeforeTextInsertedEvent* event) const 234 { 235 ASSERT(event); 236 ASSERT(renderer()); 237 int signedMaxLength = maxLength(); 238 if (signedMaxLength < 0) 239 return; 240 unsigned unsignedMaxLength = static_cast<unsigned>(signedMaxLength); 241 242 unsigned currentLength = toRenderTextControl(renderer())->text().numGraphemeClusters(); 243 unsigned selectionLength = plainText(document()->frame()->selection()->selection().toNormalizedRange().get()).numGraphemeClusters(); 244 ASSERT(currentLength >= selectionLength); 245 unsigned baseLength = currentLength - selectionLength; 246 unsigned appendableLength = unsignedMaxLength > baseLength ? unsignedMaxLength - baseLength : 0; 247 event->setText(sanitizeUserInputValue(event->text(), appendableLength)); 248 } 249 250 String HTMLTextAreaElement::sanitizeUserInputValue(const String& proposedValue, unsigned maxLength) 251 { 252 return proposedValue.left(proposedValue.numCharactersInGraphemeClusters(maxLength)); 253 } 254 255 void HTMLTextAreaElement::rendererWillBeDestroyed() 256 { 257 updateValue(); 258 } 259 260 void HTMLTextAreaElement::updateValue() const 261 { 262 if (formControlValueMatchesRenderer()) 263 return; 264 265 ASSERT(renderer()); 266 m_value = toRenderTextControl(renderer())->text(); 267 const_cast<HTMLTextAreaElement*>(this)->setFormControlValueMatchesRenderer(true); 268 notifyFormStateChanged(this); 269 m_isDirty = true; 270 } 271 272 String HTMLTextAreaElement::value() const 273 { 274 updateValue(); 275 return m_value; 276 } 277 278 void HTMLTextAreaElement::setValue(const String& value) 279 { 280 setNonDirtyValue(value); 281 m_isDirty = true; 282 } 283 284 void HTMLTextAreaElement::setNonDirtyValue(const String& value) 285 { 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'); 291 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()) 295 return; 296 297 m_value = normalizedValue; 298 m_isDirty = false; 299 setFormControlValueMatchesRenderer(true); 300 updatePlaceholderVisibility(false); 301 if (inDocument()) 302 document()->updateStyleIfNeeded(); 303 if (renderer()) 304 renderer()->updateFromElement(); 305 306 // Set the caret to the end of the text value. 307 if (document()->focusedNode() == this) { 308 #ifdef ANDROID_ACCEPT_CHANGES_TO_FOCUSED_TEXTFIELDS 309 // Make sure our UI side textfield changes to match the RenderTextControl 310 android::WebViewCore::getWebViewCore(document()->view())->updateTextfield(this, false, value); 311 #endif 312 unsigned endOfString = m_value.length(); 313 setSelectionRange(endOfString, endOfString); 314 } 315 316 setNeedsValidityCheck(); 317 notifyFormStateChanged(this); 318 } 319 320 String HTMLTextAreaElement::defaultValue() const 321 { 322 String value = ""; 323 324 // Since there may be comments, ignore nodes other than text nodes. 325 for (Node* n = firstChild(); n; n = n->nextSibling()) { 326 if (n->isTextNode()) 327 value += static_cast<Text*>(n)->data(); 328 } 329 330 UChar firstCharacter = value[0]; 331 if (firstCharacter == '\r' && value[1] == '\n') 332 value.remove(0, 2); 333 else if (firstCharacter == '\r' || firstCharacter == '\n') 334 value.remove(0, 1); 335 336 return value; 337 } 338 339 void HTMLTextAreaElement::setDefaultValue(const String& defaultValue) 340 { 341 // To preserve comments, remove only the text nodes, then add a single text node. 342 343 Vector<RefPtr<Node> > textNodes; 344 for (Node* n = firstChild(); n; n = n->nextSibling()) { 345 if (n->isTextNode()) 346 textNodes.append(n); 347 } 348 ExceptionCode ec; 349 size_t size = textNodes.size(); 350 for (size_t i = 0; i < size; ++i) 351 removeChild(textNodes[i].get(), ec); 352 353 // Normalize line endings. 354 // Add an extra line break if the string starts with one, since 355 // the code to read default values from the DOM strips the leading one. 356 String value = defaultValue; 357 value.replace("\r\n", "\n"); 358 value.replace('\r', '\n'); 359 if (value[0] == '\n') 360 value = "\n" + value; 361 362 insertBefore(document()->createTextNode(value), firstChild(), ec); 363 364 setNonDirtyValue(value); 365 } 366 367 int HTMLTextAreaElement::maxLength() const 368 { 369 bool ok; 370 int value = getAttribute(maxlengthAttr).string().toInt(&ok); 371 return ok && value >= 0 ? value : -1; 372 } 373 374 void HTMLTextAreaElement::setMaxLength(int newValue, ExceptionCode& ec) 375 { 376 if (newValue < 0) 377 ec = INDEX_SIZE_ERR; 378 else 379 setAttribute(maxlengthAttr, String::number(newValue)); 380 } 381 382 bool HTMLTextAreaElement::tooLong() const 383 { 384 // Return false for the default value even if it is longer than maxLength. 385 if (!m_isDirty) 386 return false; 387 388 int max = maxLength(); 389 if (max < 0) 390 return false; 391 return value().numGraphemeClusters() > static_cast<unsigned>(max); 392 } 393 394 void HTMLTextAreaElement::accessKeyAction(bool) 395 { 396 focus(); 397 } 398 399 const AtomicString& HTMLTextAreaElement::accessKey() const 400 { 401 return getAttribute(accesskeyAttr); 402 } 403 404 void HTMLTextAreaElement::setAccessKey(const String& value) 405 { 406 setAttribute(accesskeyAttr, value); 407 } 408 409 void HTMLTextAreaElement::setCols(int cols) 410 { 411 setAttribute(colsAttr, String::number(cols)); 412 } 413 414 void HTMLTextAreaElement::setRows(int rows) 415 { 416 setAttribute(rowsAttr, String::number(rows)); 417 } 418 419 bool HTMLTextAreaElement::shouldUseInputMethod() const 420 { 421 return true; 422 } 423 424 } // namespace 425