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